/* eslint-disable no-await-in-loop */
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-use-before-define */
/* eslint-disable no-bitwise */
/**
 * 测试用例模型
 *
 * Created by snowingsea on 2020/02/13.
 */
const fs = require('fs-extra');
const path = require('path');
const randomstring = require('randomstring');
const mongoose = require('mongoose');
const { TestCaseTransformer, IntegrationTestCaseTransformer } = require('smart-rocket-unit');
const nodeXlsx = require('node-xlsx');
const logger = require('./logger');
const TestCase = require('./mongo/test-case');
const TestCaseGroup = require('./mongo/test-case-groups');
const Func = require('./mongo/function');
const FunctionCFG = require('./mongo/function-cfg');
const FunctionVariable = require('./mongo/function-variable');
const File = require('./mongo/file');
const Filter = require('./mongo/filter');
const UserDefinedFields = require('./mongo/user-defined-fields');
const PhoenixError = require('./phoenix-error');
const publisher = require('./redis/publisher');
const common = require('./common');
const utility = require('./utility');
const numbers = require('./numbers');
const codeDefects = require('./code-defects');
const functions = require('./functions');
const projectVersions = require('./project-versions');
const FileGlobalVariable = require('./mongo/file-global-variable');
const TestCaseFieldConfig = require('./mongo/test-case-field-config');
const TestCaseCoveredField = require('./mongo/test-case-covered-field');
const BreakPoint = require('./mongo/break-point');
const ExcelModel = require('./export/report/adapter/excel');
const fileModel = require('./files');
const breakPoints = require('./break-points');
const cacheUtil = require('../cacheUtil');
const TestCaseGroupModel = require('./test-case-groups');
const handleIntegrationVariables = require('./handle-integration-variables');
const Excel = require('./export/report/adapter/excel');
const userDefinedFieldsModel = require('./user-defined-fields');
const { eventHub, eventKind } = require('./event-hub');
const invokeRelation = require('./invoke-relations');
const fileElement = require('./file-elements');
const tasks = require('./tasks');
const ProjectVersion = require('./mongo/project-version');
const FileComment = require('./mongo/file-comment');
// eslint-disable-next-line no-unused-vars
const ProgressIndicator = require('./progress-indicator');
const testCaseGroups = require('./test-case-groups');
const operationLogs = require('./operation-logs');
const breakPointModel = require('./break-points');

/**
 * 覆盖准则类型
 * @type {Object}
 */
const coverageKind = {
  all: 0,
  statement: 1,
  branch: 1 << 1,
  condition: 1 << 5, // 条件覆盖
  decision: 1 << 6,
  mcdc: 1 << 2,
  invoke: 1 << 3, // 调用覆盖
  invokedFunc: 1 << 4, // 函数覆盖
};
coverageKind.all = coverageKind.statement
  | coverageKind.branch
  | coverageKind.mcdc
  | coverageKind.invoke
  | coverageKind.invokedFunc
  | coverageKind.condition
  | coverageKind.decision;

const mcdcKind = {
  masking: 1,
  condition: 2,
};
/**
 * 测试用例类型
 * @type {Object}
 */
const testKind = {
  modified: 0,
  statement: coverageKind.statement,
  branch: coverageKind.branch,
  mcdc: coverageKind.mcdc,
  suspended: 1 << 10,
  deadEnd: 1 << 20,
  divideZero: 1 << 21,
  nullPointer: 1 << 22,
  outOfBounds: 1 << 23,
  borders: 1 << 29, // 边界用例
  timeout: 1 << 30,
};

const TestMethod = {
  Equivalence_Partition: 1, // 等价类划分
  Boundary_Analysis: 2, // 边界值分析
  Error_Guess: 3, // 错误猜测
  Program_Insertion: 4, // 程序插桩
  Functional_Decomposition: 5, // 功能分解
};

// 用例分组
const testType = {
  gn: 'GN', // 功能
  jk: 'JK', // 接口
  bj: 'BJ', // 边界
  lj: 'LJ', // 逻辑
  xn: 'XN', // 性能
};

/**
 * 测试用例执行结果类型
 * @type {Object}
 */
const testResultKind = {
  unknown: 0, // 未执行
  succeed: 1, // 执行成功
  failed: 2, // 执行失败
  noResult: 3, // 无结果
  nonStrictSucceed: 4, // 非严格通过
  runtimeError: 101, // 运行时错误
  timeout: 102, // 超时
  systemError: 111,
  instrumentError: 112,
  compileError: 113,
};

/**
 * 桩类型
 * @type {Object}
 */
const stubKind = {
  value: 1, // 赋值打桩
  code: 2, // 代码打桩
};

/**
 * 桩触发类型
 * @type {Object}
 */
const stubTriggerKind = {
  parse: 1, // 代码解析的桩函数
  manual: 2, // 手动添加的桩函数
  constructor: 3, // 构造函数插桩
};
/**
 * 测试用例参数类型
 * @type {Object}
 */
const testParameterKind = {
  globalVariable: 1, // 全局变量
  stubFunction: 2, // 桩函数
  fixedAddrsBase: 21, // 绝对地址基地址

};
/**
 * 获取概要json信息
 * @param {TestCase} testCase 测试用例
 * @param {Object} locale 国际化
 * @returns {Object}
 */
function getSummaryJson(testCase, locale) {
  const json = {
    testId: testCase._id, // eslint-disable-line no-underscore-dangle
    functionId: testCase.functionId,
    execCount: testCase.execCount || 1,
    kind: testCase.kind,
    description: testCase.description ? testCase.description : '',
    requirementInfo: testCase.requirementInfo ? testCase.requirementInfo : '',
    result: {
      kind: testCase.resultKind,
    },
    userDefinedFields: testCase.userDefinedFields,
    number: testCase.number,
    typeName: testCase.typeName,
    typeNumber: testCase.typeNumber,
    typeNumberString: testCase.typeNumberString,
    testMethod: testCase.testMethod,
  };
  if (testCase.identifier) {
    json.identifier = testCase.identifier;
  }
  if (testCase.log) {
    json.result.log = testCase.log.message;
    json.result.location = testCase.log.location;
  }
  if (testCase.kind !== testKind.modified && testCase.kind) {
    json.description = locale.__('tests.structuralCoverageCase'); // eslint-disable-line no-underscore-dangle
  }
  if (json.description !== testCase.description) {
    TestCase.updateOne({ _id: json.testId }, { description: json.description })
      .then(() => { })
      .catch(() => { });
  }
  return json;
}

/**
 * 清空无数据的字段
 * @param {Object} data 数据
 */
function removeEmptyField(data) {
  if (!data) {
    return;
  }
  if (utility.isArrayType(data)) {
    data.forEach((item) => {
      removeEmptyField(item);
    });
  } else if (utility.isObjectType(data)) {
    Object.keys(data).forEach((key) => {
      removeEmptyField(data[key]);
      if (typeof data[key] !== 'boolean') {
        if (utility.isObjectType(data[key]) && Object.keys(data[key]).length === 0) {
          delete data[key];
        } else if (utility.isArrayType(data[key]) && data[key].length === 0) {
          delete data[key];
        } else if (!data[key] && data[key] !== 0) {
          delete data[key];
        }
      }
    });
  }
}

function hasValue(data) {
  let childHasValue = false;
  if (utility.isArrayType(data)) {
    data.forEach((item) => {
      if (!childHasValue) {
        childHasValue = hasValue(item);
      }
    });
  } else if (utility.isObjectType(data)) {
    Object.keys(data).forEach((key) => {
      if (key === '@value' && data[key] !== '') {
        childHasValue = true;
        return;
      }
      if (!childHasValue) {
        childHasValue = hasValue(data[key]);
      }
    });
  } else {
    return false;
  }
  return childHasValue;
}

/**
 * 获取json信息
 * @param {TestCase} testCase 测试用例
 * @param {Object} locale 国际化
 * @returns {Object}
 */
function getJson(testCase, locale) {
  const json = getSummaryJson(testCase, locale);
  removeEmptyField(testCase.data);
  json.data = testCase.data || {};
  json.typeNumber = json.typeNumberString;
  delete json.typeNumberString;
  return json;
}

function getTestIdentifier(test) {
  if (!test.identifierNumber) {
    return '';
  }
  const identifier = [];
  if (utility.isEmpty(test.testIdentifierRule)) {
    if (test.functionName) {
      identifier.push(test.functionName);
    }
    if (test.filepath) {
      test.filepath.split('/').forEach((directory, index) => {
        // 过滤根目录
        if (index === 0) {
          return;
        }
        identifier.push(directory);
      });
    }
    identifier.push(test.identifierNumber);
    return identifier.join('_');
  }
  if (test.testIdentifierRule.prefix !== '') {
    identifier.push(test.testIdentifierRule.prefix);
  }
  if (test.testIdentifierRule.numLength) {
    identifier.push(test.identifierNumber.toString().padStart(test.testIdentifierRule.numLength, '0'));
  }
  return identifier.join('');
}
// 614自动填充caseId值
async function autoAddCaseId(testCaseId) {
  const {
    versionId, userDefinedFields, identifierNumber, fileId, functionId,
  } = await TestCase.findById(testCaseId);
  const { versionType, testIdentifierRule } = await projectVersions.getVersion(versionId);
  const { functionName } = await Func.findById(functionId);
  const { path } = await File.findById(fileId);
  const testIdentifier = getTestIdentifier({
    filepath: path, identifierNumber, functionName, testIdentifierRule,
  });
  const testCaseUserDefinedFields = await userDefinedFieldsModel.listUserDefinedField(versionType, 'testcase');
  const userDefinedCaseIdField = testCaseUserDefinedFields.list.find((item) => item.name === 'CaseID');
  if (userDefinedCaseIdField) {
    let existCaseId = false;
    userDefinedFields.forEach((item) => {
      if (item.name === 'CaseID') {
        existCaseId = true;
        if (!item.value) {
          item.value = testIdentifier;
        }
      }
    });
    if (!existCaseId) {
      userDefinedFields.push({
        value: testIdentifier,
        key: userDefinedCaseIdField._id,
        name: 'CaseID',
      });
    }
    await TestCase.updateOne({ _id: mongoose.Types.ObjectId(testCaseId) }, { userDefinedFields });
  }
}
/**
 * 获取测试用例
 * @param {String} testId 测试用例id
 * @return {Promise}
 */
async function getTest(testId) {
  const [exists] = await TestCase.aggregate(
    [
      { $match: { _id: mongoose.Types.ObjectId(testId) } },
      {
        $lookup: {
          from: 'projectversions',
          localField: 'versionId',
          foreignField: '_id',
          as: 'versionType',
        },
      },
      { $unwind: '$versionType' },
      {
        $addFields: {
          testIdentifierRule: '$versionType.testIdentifierRule',
          versionType: '$versionType.versionType',
        },
      },
      {
        $lookup: {
          from: 'files',
          localField: 'fileId',
          foreignField: '_id',
          as: 'filepath',
        },
      },
      { $unwind: '$filepath' },
      {
        $addFields: { filepath: '$filepath.path' },
      },
      {
        $lookup: {
          from: 'functions',
          localField: 'functionId',
          foreignField: '_id',
          as: 'functionName',
        },
      },
      { $unwind: '$functionName' },
      {
        $addFields: { functionName: '$functionName.functionName' },
      },
    ]
  );
  if (!exists) {
    return null;
  }
  exists.identifier = getTestIdentifier(exists);
  const cond = { scope: 'testcase', $or: [{ domain: exists.versionType }] };
  // 兼容老的数据
  if (exists.versionType === projectVersions.versionType.unitTest) {
    cond.$or.push({ domain: { $exists: false } });
  }
  const udfs = await UserDefinedFields.find(cond, {}, { sort: { index: 1 } });
  const userDefinedFields = exists.userDefinedFields || [];
  exists.userDefinedFields = udfs.map(({ _id, name: displayName }) => {
    const assigned = userDefinedFields.find((u) => _id.equals(u.key));
    return {
      key: _id,
      name: displayName,
      value: assigned ? assigned.value : null,
    };
  });
  // 兼容旧数据
  exists.testMethod = exists.testMethod || [];
  return exists;
}

/**
 * 获取测试用例集合
 * @param {Array} testIds 用例id列表
 * @param {Array} exceptTestIds 不计入的用例id列表
 * @param {Object} options 选项
 * @return {Promise}
 */
function getTests({ testIds = null, exceptTestIds = null }, options = {}) {
  if (testIds) {
    if (!utility.isArrayType(testIds)) {
      testIds = [testIds];
    }
    testIds = testIds.map((id) => mongoose.Types.ObjectId(id));
    options._id = { $in: testIds }; // eslint-disable-line no-underscore-dangle
  } else {
    if (!exceptTestIds) {
      exceptTestIds = [];
    }
    if (!utility.isArrayType(exceptTestIds)) {
      exceptTestIds = [exceptTestIds];
    }
    exceptTestIds = exceptTestIds.map((id) => mongoose.Types.ObjectId(id));
    options._id = { $nin: exceptTestIds }; // eslint-disable-line no-underscore-dangle
  }
  return TestCase.find(options);
}

/**
 * 获取测试用例列表请求约束
 * @param {Array|Number} status 测试用例状态
 * @return {Object}
 */
function getTestOptions({ status, kind, resultKind }) {
  const options = {};
  // 项目测试用例状态
  if (status) {
    options.status = { $in: [].concat(parseInt(status, 10)) };
  }
  if (kind) {
    const kinds = [].concat(kind).map((i) => parseInt(i, 10));
    options.$or = [];
    const sum = kinds.reduce((sum, i) => sum | i, 0);
    if (sum) {
      options.$or.push({ kind: { $bitsAnySet: sum } });
    }
    if (kinds.indexOf(0) > -1) {
      // 包含了0的情况，特殊处理
      options.$or.push({ kind: 0 });
    }
  }
  if (resultKind) {
    options.resultKind = { $in: [].concat(resultKind) };
  }
  return options;
}

/**
 * 获取函数的测试用例集合
 * @param {Array|String} functionIds 函数id列表
 * @return {Promise}
 */
function getTestsOfFunctions(functionIds,
  {
    idOnly = false,
  } = {}) {
  if (!functionIds) {
    functionIds = [];
  }
  if (!utility.isArrayType(functionIds)) {
    functionIds = [functionIds];
  }
  functionIds = functionIds.map((id) => mongoose.Types.ObjectId(id));
  if (idOnly) {
    return TestCase.find({ functionId: { $in: functionIds } }, { _id: 1 }).lean();
  }
  return TestCase.find({ functionId: { $in: functionIds } }).lean();
}

/**
 * 获取文件的测试用例集合
 * @param {String} fileId 文件id
 * @return {Promise}
 */
function getTestsOfFile(fileId) {
  return TestCase.aggregate([
    {
      $match: { fileId: mongoose.Types.ObjectId(fileId) },
    },
    {
      $lookup: {
        from: 'functions',
        localField: 'functionId',
        foreignField: '_id',
        as: 'function',
      },
    },
    {
      $unwind: '$function',
    },
    {
      $match: { 'function.collectCoverage': true },
    },
    {
      $unset: ['function'],
    },
  ]).then((testCases) => testCases.sort((a, b) => (a.number > b.number ? 1 : -1)));
}

/**
 * 筛选测试用例列表
 * @param {Object} pagination 分页信息
 * @param {Object} options 用户筛选项
 * @return {Promise}
 */
function filter(pagination = {}, options = {}) {
  const { sortBy } = pagination;
  if (!sortBy) {
    pagination.sortBy = 'number';
  }
  if ((sortBy === 'testId') || (sortBy === 'createAt')) {
    pagination.sortBy = '_id';
  }
  return Filter.filter(TestCase, pagination, options);
}

/**
 * 用例数
 * @param {Object} options 用户筛选项
 * @return {Promise}
 */
function getCount(options = {}) {
  return TestCase.countDocuments(options);
}

/**
 * 获取测试用例分组统计
 * @param {String} versionId 项目版本id
 * @param {String} key 要分组的字段名
 * @return {Promise}
 */
function getGroupCount(versionId, key) {
  const options = {
    versionId: mongoose.Types.ObjectId(versionId),
  };
  return TestCase.aggregate([{
    $match: options,
  }, {
    $group: {
      _id: `$${key}`,
      count: { $sum: 1 },
    },
  }]);
}
/**
 * 删除用例执行结果实际值
 * @param {object} output 输出项
 */
function removeOutputResult(output) {
  if (utility.isObjectType(output)) {
    if (output['@result'] !== undefined) {
      delete output['@result'];
    }
    if (output['@isEqual'] !== undefined) {
      delete output['@isEqual'];
    }
    if (output['@isChanged'] !== undefined) {
      delete output['@isChanged'];
    }
    // 桩函数实际调用次数
    if (output['@count'] !== undefined) {
      delete output['@count'];
    }
    Object.values(output).forEach((item) => {
      removeOutputResult(item);
    });
  } else if (utility.isArrayType(output)) {
    output.forEach((item) => {
      removeOutputResult(item);
    });
  }
}
/**
 * 更新测试用例执行结果
 * @param {String} testId 测试用例id
 * @param {Number} resultKind 结果类型
 * @param {String} message 异常结果时表示异常信息
 * @param {Object} location 位置信息
 * @return {Promise}
 */
function updateTestResult(testId, resultKind, message = null, location = undefined) {
  return new Promise((resolve, reject) => {
    testId = mongoose.Types.ObjectId(testId);
    const doc = { resultKind, log: { message, location } };
    let test;
    getTest(testId)
      .then((res) => {
        test = res;
        const incDoc = { $inc: { failedCount: 0, succeedCount: 0 } };
        switch (test.resultKind) {
          case testResultKind.failed:
          case testResultKind.runtimeError:
          case testResultKind.timeout:
          case testResultKind.systemError:
          case testResultKind.instrumentError:
          case testResultKind.compileError:
          case testResultKind.noResult:
            incDoc.$inc.failedCount -= 1;
            break;
          case testResultKind.succeed:
          case testResultKind.nonStrictSucceed:
            incDoc.$inc.succeedCount -= 1;
            break;
          default:
            break;
        }
        switch (resultKind) {
          case testResultKind.failed:
          case testResultKind.runtimeError:
          case testResultKind.timeout:
          case testResultKind.systemError:
          case testResultKind.instrumentError:
          case testResultKind.compileError:
          case testResultKind.noResult:
            incDoc.$inc.failedCount += 1;
            break;
          case testResultKind.succeed:
          case testResultKind.nonStrictSucceed:
            incDoc.$inc.succeedCount += 1;
            break;
          case testResultKind.unknown:
            // 清除@result和@isEqual
            if (test.data && test.data.output) {
              removeOutputResult(test.data.output);
              doc['data.output'] = test.data.output;
            }
            break;
          default:
            break;
        }
        const promises = [
          TestCase.updateOne({ _id: testId }, doc),
          Func.updateOne({ _id: test.functionId }, incDoc),
          common.increaseFunction(test.functionId, incDoc),
        ];
        return Promise.all(promises);
      })
      .then(() => Func.findOne({ _id: test.functionId }))
      .then((func) => {
        doc.testId = testId;
        doc.functionId = test.functionId;
        doc.creatorId = func.creatorId;
        doc.kind = test.kind;
        doc.result = {
          kind: doc.resultKind,
          log: doc.log.message,
          location: doc.log.location,
        };
        publisher.publishTestCaseMessage('update', doc);
        const functionData = {
          functionId: test.functionId,
          versionId: test.versionId,
          creatorId: func.creatorId,
          fileId: func.fileId,
          failed: null,
        };
        if (func.failedCount > 0 || func.succeedCount > 0) {
          functionData.failed = func.failedCount > 0;
        }
        publisher.publishFunctionMessage('update', functionData);
        resolve();
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 删除cfg中的测试用例信息
 * @param {Object} cfg cfg数据
 * @return {Promise}
 */
async function removeCFGTestIds(functionId) {
  functionId = mongoose.Types.ObjectId(functionId);
  const [cfg, testcases] = await Promise.all([
    FunctionCFG.findOne({ functionId }),
    getTestsOfFunctions(functionId),
  ]);
  const promise = [];
  if (cfg) {
    const doc = { $unset: {} };
    if (cfg.nodes) {
      Object.keys(cfg.nodes).forEach((index) => {
        doc.$unset[`nodes.${index}.testIds`] = 1;
        if (cfg.nodes[index].statements) {
          Object.keys(cfg.nodes[index].statements).forEach((subIndex) => {
            doc.$unset[`nodes.${index}.statements.${subIndex}.testIds`] = 1;
          });
        }
      });
    }
    if (cfg.edges) {
      Object.keys(cfg.edges).forEach((index) => {
        doc.$unset[`edges.${index}.testIds`] = 1;
      });
    }
    if (cfg.branches) {
      Object.keys(cfg.branches).forEach((index) => {
        if (cfg.branches[index].branches) {
          Object.keys(cfg.branches[index].branches).forEach((branchId) => {
            doc.$unset[`branches.${index}.branches.${branchId}.testIds`] = 1;
          });
        }
      });
    }
    if (cfg.decisions) {
      Object.keys(cfg.decisions).forEach((index) => {
        if (cfg.decisions[index].cases) {
          Object.keys(cfg.decisions[index].cases).forEach((caseId) => {
            doc.$unset[`decisions.${index}.cases.${caseId}.testIds`] = 1;
          });
        }
      });
    }
    if (cfg.invokes) {
      doc.$unset.invokes = 1;
    }
    promise.push(FunctionCFG.updateOne({ functionId }, doc));
  }
  promise.push(TestCaseCoveredField.deleteMany({ testcaseId: { $in: testcases.map((test) => test._id) } }));
  return Promise.all(promise);
}

/**
 * 删除某个项目版本下用例的执行结果
 * @param {String} versionId 项目版本id
 * @return {Promise}
 */
function removeResultOfExecutionInVersion(versionId) {
  return new Promise((resolve, reject) => {
    versionId = mongoose.Types.ObjectId(versionId);
    functions.getFunctions(versionId)
      .then((funcs) => {
        const promises = [
          TestCase.updateMany({ versionId }, { resultKind: testResultKind.unknown, log: {} }),
          Func.updateMany({ versionId }, { failedCount: 0, succeedCount: 0 }),
          File.updateMany({ versionId }, { failedCount: {}, succeedCount: {} }),
          codeDefects.updateCodeDefects(
            { versionId, testcases: { $exists: true, $ne: [] } },
            { $set: { unread: true } }
          ),
        ];
        if (funcs) {
          funcs.forEach((func) => {
            promises.push(removeCFGTestIds(func._id));
          });
        }
        promises.push(invokeRelation.invokeEdgeUnCoveredByVersionId(versionId));
        return Promise.all(promises);
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 删除某个函数下用例的执行结果
 * @param {Object} func 函数
 * @return {Promise}
 */
function removeResultOfExecutionInFunction(func) {
  return new Promise((resolve, reject) => {
    const functionId = func._id;
    removeCFGTestIds(functionId)
      .then(() => invokeRelation.invokeEdgeUnCoveredByFunctionId(functionId))
      .then(() => {
        const doc = { resultKind: testResultKind.unknown, log: {}, hash: utility.shortUUID() };
        return Promise.all(
          [
            TestCase.updateMany({ functionId }, doc),
            codeDefects.updateCodeDefects(
              { functionId, testcases: { $exists: true, $ne: [] } },
              { $set: { unread: true } }
            ),
          ]
        );
      })
      .then(() => getTestsOfFunctions([functionId]))
      .then((tests) => {
        tests.forEach((test) => {
          const doc = {};
          doc.testId = test._id;
          doc.functionId = test.functionId;
          doc.creatorId = func.creatorId;
          doc.result = { kind: testResultKind.unknown };
          publisher.publishTestCaseMessage('update', doc);
        });
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 删除某个用例的执行结果
 * @param {String} testId 用例id
 * @param {TestCase} test 测试用例数据
 * @return {Promise}
 */
function removeResultOfExecution(testId, test = null) {
  return new Promise((resolve, reject) => {
    testId = mongoose.Types.ObjectId(testId);
    Promise.resolve()
      .then(() => {
        if (test) {
          return Promise.resolve(test);
        }
        return getTest(testId);
      })
      .then((res) => {
        if (res) {
          test = res;
        }
        return Promise.resolve();
      })
      .then(() => {
        if (!test) {
          return Promise.resolve();
        }
        const promises = [
          updateTestResult(testId, testResultKind.unknown),
          functions.removeCfgCoverageWithTestId(test.functionId, testId),
          codeDefects.updateCodeDefects(
            {
              functionId: test.functionId,
              testcases: { $elemMatch: { id: testId.toString() } },
            },
            { $set: { unread: true } }
          ),
          // 删除调用关系里对应的testCase覆盖
          invokeRelation.invokeEdgeUnCovered(testId, test.functionId),
        ];
        return Promise.all(promises);
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 搜索对象并收集路径
 * @param {Object} obj 搜索的对象
 * @param {String} targetString 目标字符串
 * @param {String} currentPath 当前路径
 * @param {Function} match 匹配函数
 * @param {Array} paths 收集到的路径数组
 * @returns {Array}
 */
function searchAndDelete(obj, targetString, match) {
  Object.keys(obj).forEach((key) => {
    if (utility.isObjectType(obj[key])) {
      searchAndDelete(obj[key], targetString, match);
    } else if (key === '@value' && match(obj[key], targetString)) {
      delete obj[key];
    }
  });
}

/**
 * 匹配指针目标名称
 * @param {String} str 待匹配字符串
 * @param {String} target 指针目标名称
 * @returns {boolean}
 */
function matchMallocName(str, target) {
  if (str.startsWith('&')) {
    str = str.substring(1);
  }
  if (str.endsWith(']')) {
    [str] = str.split('[');
  }
  return str === target;
}

/**
 * 匹配桩函数指针目标名
 * @param x 待匹配字符串
 * @param target 桩函数指针目标名
 * @returns {boolean}
 */
function matchStubPointerTargetName(x, target) {
  if (x.startsWith('@')) {
    x = x.substring(1);
  }
  return x === target;
}

/**
 * 查找绝对地址名对应表达式
 * @param {String} functionId 函数id
 * @param {String} fixedAddressName 绝对地址名
 * @return {Promise<Array>}
 */
function findFixedAddrsExprs(functionId, fixedAddressName) {
  return new Promise((resolve, reject) => {
    functionId = mongoose.Types.ObjectId(functionId);
    TestCase.aggregate(
      [
        {
          $match: {
            functionId,
          },
        },
        {
          $addFields: {
            fixedAddrs_exprs: { $objectToArray: '$data.fixedAddrs.exprs' },
          },
        }, {
          $match: {
            'fixedAddrs_exprs.v.@value': fixedAddressName,
          },
        }],
    )
      .then((res) => {
        const keys = [];
        res.forEach((test) => {
          test.fixedAddrs_exprs.forEach((expr) => {
            if (expr.v['@value'] === fixedAddressName) {
              // eslint-disable-next-line no-underscore-dangle
              keys.push({ expr: expr.k, testId: test._id });
            }
          });
        });
        resolve(keys);
      })
      .catch(reject);
  });
}

async function removeFixedAddressBase(functionId, fixedAddressNameHex) {
  const fixedAddressNameDec = parseInt(fixedAddressNameHex, 16).toString(10);
  functionId = mongoose.Types.ObjectId(functionId);
  await TestCase.updateOne({
    functionId,
    [`data.fixedAddrs.bases.${fixedAddressNameDec}`]: { $exists: true },
  }, {
    $unset: {
      [`data.fixedAddrs.bases.${fixedAddressNameDec}`]: 1,
    },
  });
}

/**
 * 移除测试用例中的绝对地址数据
 * @param {String} functionId 绝对地址Id
 * @param {String} fixedAddressNameOrg 绝对地址名
 * @param {String} fixedAddressNameDec 绝对地址名十进制
 * @return {Promise}
 */
function removeFixedAddress(functionId, fixedAddressNameOrg, fixedAddressNameDec) {
  return new Promise((resolve, reject) => {
    findFixedAddrsExprs(functionId, fixedAddressNameOrg)
      .then((keys) => {
        const promises = [];
        const modifiedTestIds = [];
        keys.forEach((key) => {
          const doc = { $unset: {}, $set: { kind: testKind.modified } };
          doc.$unset[`data.fixedAddrs.bases.${fixedAddressNameDec}`] = 1;
          const { testId, expr } = key;
          doc.$unset[`data.fixedAddrs.exprs.${expr}`] = 1;
          promises.push(TestCase.updateOne({ _id: mongoose.Types.ObjectId(testId) }, doc));
          modifiedTestIds.push(mongoose.Types.ObjectId(testId));
        });
        promises.push(codeDefects.setCodeDefectToManual({ functionId, testIds: modifiedTestIds }));
        return Promise.all(promises);
      })
      // 前面只是删除了当前选中该fixedAddressBase, 所以还需要删除当前未选中该fixedAddressBase的
      .then(() => removeFixedAddressBase(functionId, fixedAddressNameOrg))
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 移除测试用例中的绝对地址偏移量值
 * @param {String} functionId 绝对地址Id
 * @param {String} fixedAddressName 绝对地址名
 * @param {String} offsetName 指针目标偏移量名
 * @return {Promise}
 */
function removeFixedAddressValue(functionId, fixedAddressName, offsetName) {
  functionId = mongoose.Types.ObjectId(functionId);
  const doc = { $unset: {}, $set: { kind: testKind.modified } };
  doc.$unset[`data.fixedAddrs.bases.${fixedAddressName}.pointerTargets.${offsetName}`] = 1;
  return Promise.all([
    TestCase.updateMany({ functionId }, doc),
    codeDefects.setCodeDefectToManual({ functionId }),
  ]);
}

/**
 * 批量移除测试用例中的绝对地址偏移量值
 * @param {String} functionId 绝对地址Id
 * @param {String} fixedAddressName 绝对地址名
 * @return {Promise}
 */
function removeFixedAddressPointerTargets(functionId, fixedAddressName) {
  functionId = mongoose.Types.ObjectId(functionId);
  const doc = { $unset: {}, $set: { kind: testKind.modified } };
  doc.$unset[`data.fixedAddrs.bases.${fixedAddressName}.pointerTargets`] = 1;
  return Promise.all([
    TestCase.updateMany({ functionId }, doc),
    codeDefects.setCodeDefectToManual({ functionId }),
  ]);
}

/**
 * 批量移除指定测试用例中的绝对地址偏移量值
 * @param {String} functionId 函数Id
 * @param {String} fixedAddressName 绝对地址名
 * @param {Array} offsets 指针目标偏移量数组
 * @param {Object} outputFixedAddress 输出检查项中的绝对地址
 * @return {Promise}
 */
function removeFixedAddressValues(
  functionId,
  fixedAddressName,
  offsets,
  outputFixedAddress = undefined,
) {
  const doc = { $unset: {}, $set: { kind: testKind.modified } };
  functionId = mongoose.Types.ObjectId(functionId);
  offsets.forEach((offsetName) => {
    doc.$unset[`data.fixedAddrs.bases.${fixedAddressName}.pointerTargets.${offsetName}`] = 1;
    if (outputFixedAddress) {
      doc.$unset[`data.output.fixedAddrs.${fixedAddressName}.pointerTargets.${offsetName}`] = 1;
    }
  });
  return Promise.all([
    TestCase.updateMany({ functionId }, doc),
    codeDefects.setCodeDefectToManual({ functionId }),
  ]);
}

/**
 * 移除指定测试用例中的绝对地址表达式
 * @param {String} functionId 函数Id
 * @param {String} exprName 绝对地址表达式
 * @return {Promise}
 */
function removeFixedAddressExpr(
  functionId,
  exprName
) {
  const doc = { $unset: {}, $set: { kind: testKind.modified } };
  functionId = mongoose.Types.ObjectId(functionId);
  doc.$unset[`data.fixedAddrs.exprs.${exprName}`] = 1;
  return Promise.all([
    TestCase.updateMany({ functionId }, doc),
    codeDefects.setCodeDefectToManual({ functionId }),
  ]);
}

/**
 * 批量移除指定测试用例中的绝对地址偏移量的@value值
 * @param {String} functionId 绝对地址Id
 * @param {String} fixedAddressName 绝对地址名
 * @param {String} name 偏移量名
 * @param {String} newLength 偏移量长度
 * @param {Object} outputFixedAddress 输出检查项中的绝对地址
 * @return {Promise}
 */
function removeFixedAddressPointerTargetsValues(
  functionId,
  { fixedAddressName, name, newLength },
  outputFixedAddress = undefined,
) {
  return new Promise((resolve, reject) => {
    functionId = mongoose.Types.ObjectId(functionId);
    getTestsOfFunctions(functionId)
      .then((testCases) => {
        const promises = [];
        testCases.forEach((testCase) => {
          if (!testCase.data.fixedAddrs.bases) {
            return;
          }
          const doc = { $unset: {}, $set: {} };
          Object.keys(testCase.data.fixedAddrs.bases).forEach((fixedAddrsName) => {
            if (fixedAddrsName !== fixedAddressName) {
              return;
            }
            const { pointerTargets } = testCase.data.fixedAddrs.bases[fixedAddrsName];
            if (pointerTargets[name]) {
              Object.keys(pointerTargets[name]['@value']).forEach((index) => {
                if (parseInt(index, 10) >= newLength) {
                  doc.$unset[`data.fixedAddrs.bases.${fixedAddrsName}.pointerTargets.${name}.@value.${index}`] = 1;
                  if (outputFixedAddress) {
                    doc.$unset[`data.output.fixedAddrs.${fixedAddrsName}.pointerTargets.${name}.@value.${index}`] = 1;
                  }
                }
              });
            }
          });
          const { _id } = testCase; // eslint-disable-line no-underscore-dangle
          promises.push(TestCase.updateOne({ _id }, doc));
        });
        return Promise.all(promises);
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 移除测试用例中的全局检查项
 * @param {String} functionId 函数id
 * @param {String} variableName 变量名
 * @return {Promise}
 */
function removeOutputGlobalVariable(functionId, variableName) {
  const doc = { $unset: {} };
  doc.$unset[`data.output.global.${variableName}`] = 1;
  return TestCase.updateMany({ functionId }, doc);
}

/**
 * 移除测试用例中的静态变量检查项
 * @param {String} functionId 函数id
 * @param {String} variableName 变量名
 * @return {Promise}
 */
function removeOutputStaticsVariable(functionId, variableName) {
  const doc = { $unset: {} };
  doc.$unset[`data.output.statics.${variableName}`] = 1;
  return TestCase.updateMany({ functionId }, doc);
}
/**
 * 移除测试用例中的指针目标检查项
 * @param {String} functionId 函数id
 * @param {String} variableName 变量名
 * @return {Promise}
 */
function removeOutputMallocVariable(functionId, variableName) {
  const doc = { $unset: {} };
  doc.$unset[`data.output.malloc.${variableName}`] = 1;
  return TestCase.updateMany({ functionId }, doc);
}

/**
 * 移除测试用例中的成员函数所属类检查项
 * @param {String} functionId 函数id
 * @return {Promise}
 */
function removeOutputObjectVariable(functionId) {
  const doc = { $unset: {} };
  doc.$unset['data.output.object'] = 1;
  return TestCase.updateMany({ functionId }, doc);
}

/**
 * 移除测试用例中的绝对地址检查项
 * @param {String} functionId 函数id
 * @param {String} variableName 变量名
 * @return {Promise}
 */
function removeOutputFixedAddress(functionId, variableName) {
  const doc = { $unset: {} };
  doc.$unset[`data.output.fixedAddrs.${variableName}`] = 1;
  return TestCase.updateMany({ functionId }, doc);
}

function removeOutputIntegratedFunc(functionId, mangledId) {
  const doc = { $unset: {} };
  doc.$unset[`data.output.${functions.integratedFuncKey}.${mangledId}`] = 1;
  return TestCase.updateMany({ functionId: mongoose.Types.ObjectId(functionId) }, doc);
}

/**
 * 删除测试用例
 * @param {String} testId 测试用例id
 * @param {TestCase} test 测试用例数据
 * @return {Promise}
 */
function removeTest(testId, test = null) {
  testId = mongoose.Types.ObjectId(testId);
  return Promise.resolve()
    .then(() => Promise.all([
      BreakPoint.deleteMany({ testcaseId: testId }),
    ]))
    .then(() => {
      if (test) {
        return Promise.resolve(test);
      }
      return getTest(testId);
    })
    .then((res) => {
      test = res;
      const options = {
        functionId: test.functionId,
        testcases: { $elemMatch: { id: test._id.toString() } },
        manual: false,
      };
      let operationQueue = Promise.resolve();
      if (test.kind !== testKind.modified) {
        operationQueue = codeDefects.deleteCodeDefects(options);
      }
      delete options.manual;
      return operationQueue.then(() => codeDefects.updateCodeDefects(options, {
        $pull: { testcases: { id: test._id } },
        $set: { unread: true },
      }));
    })
    .then(() => removeResultOfExecution(testId))
    .then(() => TestCase.deleteOne({ _id: testId }))
    .then(() => (test.typeName ? resetTestTypeNumberSequentially(test.functionId, test.typeName) : Promise.resolve()))
    .then(() => {
      const data = {
        testId,
        functionId: test.functionId,
      };
      // TODO：补全creatorId
      publisher.publishTestCaseMessage('delete', data);
    })
    .then(() => TestCaseGroupModel.clearEmptyTestGroups(testId, 'testcase', test.functionId))
    .then(({ groupId }) => groupId);
}

async function removeTestsByFunction(functionId) {
  functionId = mongoose.Types.ObjectId(functionId);
  const testCases = await TestCase.find({ functionId }).lean();
  await Promise.all(testCases.map((tc) => removeTest(tc._id, tc)));
}

/**
 * 判断是否是桩代码键
 * @param {String} key 键
 * @return {boolean}
 */
function isStubBodyKey(key) {
  const parts = key.split('.');
  if (parts.length !== 3) {
    return false;
  }
  return parts[0] === 'stubs' && parts[2] === 'body';
}

/**
 * 判断是否是桩调用次数键
 * @param {String} key 键
 * @return {boolean}
 */
function isStubTimeKey(key) {
  const parts = key.split('.');
  if (parts.length !== 3) {
    return false;
  }
  return parts[0] === 'stubs' && parts[2] === 'times';
}

/**
 * 判断是否是类类型构造函数
 * @param {String} key 键
 * @return {boolean}
 */
function isClassInitKey(key) {
  return key.indexOf('@init.@ctor.@value') !== -1;
}

/**
 * 判断是否是类类型数组构造函数
 * @param {String} key 键
 * @return {boolean}
 */
function isClassArrayInitKey(key) {
  return key.indexOf('@init.#.@init.@ctor.@value') !== -1 || /@init\.\d+\.@init\.@ctor\.@value/.test(key);
}
/**
 * 更新测试用例数组下标
 * @param {String} testId 测试用例Id
 * @param {String} fieldPath 路径
 * @param {String} val 值
 * @param {String} stubNameFieldPath 桩函数上层路径
 * @param {Array[String]} times 桩函数执行次数
 */
async function updateArrayIndexRange(testId, fieldPath, val, stubNameFieldPath, times, reserveOldValues = true) {
  // val格式校验
  const reg = /^(\d+,)*\d+$/;
  const reg1 = /^\d+-\d+$/;
  let keys = [];
  let indexes = [];
  if (reg.test(val)) {
    indexes = val.split(',').map((val) => parseInt(val, 10));
    if (indexes.length > 100) {
      throw new PhoenixError('invalid request', 'index range must be less than 100');
    }
    // 判断是否有重复
    if (new Set(indexes).size < indexes.length) {
      throw new PhoenixError('invalid request', 'index duplicated');
    }
  } else if (reg1.test(val)) {
    const [start, end] = val.split('-').map((val) => parseInt(val, 10));
    if (start >= end) {
      throw new PhoenixError('invalid request', 'range start should be less than end');
    }
    if (end - start > 99) {
      throw new PhoenixError('invalid request', 'index range must be less than 100');
    }
    for (let index = start; index <= end; index += 1) {
      indexes.push(index);
    }
  } else {
    throw new PhoenixError('invalid request', 'malformed value');
  }
  // 处理桩函数数组下标
  if (stubNameFieldPath && times) {
    let appendStr;
    times.forEach((time) => {
      const timeBaseFieldPath = `${stubNameFieldPath}.@value.${time}.`;
      if (fieldPath.match(timeBaseFieldPath)) {
        appendStr = fieldPath.replace(timeBaseFieldPath, '');
      }
    });
    if (appendStr) {
      keys = times.map((time) => `${stubNameFieldPath}.@value.${time}.${appendStr}`);
    }
  } else {
    keys.push(fieldPath);
  }
  if (keys.length === 0) {
    return Promise.resolve();
  }
  if (reserveOldValues) {
    // 默认保留旧值，仅更新__indexRange
    return TestCase.updateOne({ _id: testId }, {
      $set: keys.reduce((obj, key) => {
        const k = `data.${key}.__indexRange`;
        obj[k] = val;
        return obj;
      }, {}),
    });
  }
  const pipelineDoc = [
    {
      // 旧的value字段备份
      $set: keys.reduce((obj, key) => {
        const k = `data.${key}.@value__bak`;
        obj[k] = `$data.${key}.@value`;
        return obj;
      }, {}),
    },
    {
      // 删除旧的value字段
      $unset: keys.map((key) => `data.${key}.@value`),
    },
    {
      // 按照新的数组下标，从旧的value中取出对应的值(如果不存在则取出undefined)，保存到新的value字段
      $set: keys.reduce((obj, key) => {
        obj[`data.${key}.__indexRange`] = val;
        obj[`data.${key}.@value`] = indexes.reduce((indexObj, index) => {
          indexObj[index] = `$data.${key}.@value__bak.${index}`;
          return indexObj;
        }, {});
        return obj;
      }, {}),
    },
    {
      // 删除备份value字段
      $unset: keys.map((key) => `data.${key}.@value__bak`),
    },
  ];
  return TestCase.updateOne({ _id: testId }, pipelineDoc);
}

/**
 * 局部更新测试用例
 * @param {String} testId 测试用例id
 * @param {String} key 测试用例路径
 * @param {String} value 测试用例局部值
 * @param {Object} data 测试用例数据
 * @param {String} description 测试用例描述
 * @param {String} requirementInfo 需求关联信息
 * @param {Number} execCount 执行次数
 * @param {Boolean} testcaseImport 是否是用例上传导致的用例更新
 * @param {Boolean} updateSourceInRegression 是否更新字段：回归版本中用例变更
 * @param {Array} testMethod 测试方法
 * @return {Promise}
 */
async function updateTest(testId, {
  key = undefined,
  value = undefined,
  data = undefined,
  description = undefined, // eslint-disable-line comma-dangle
  requirementInfo = undefined,
  userDefinedFields = undefined,
  execCount = undefined,
  testcaseImport = false,
  updateSourceInRegression = false,
  typeName, // 用例分组
  testMethod = undefined,
}) {
  if (typeName && !Object.values(testType).includes(typeName)) {
    throw new PhoenixError('invalid request', `test type must one of ${Object.values(testType)}`);
  }
  if (!key && !data && !description && !requirementInfo && !userDefinedFields && !execCount && !typeName && !testMethod) {
    return Promise.resolve();
  }
  const originalTest = await getTest(testId);
  const doc = { $unset: {} };
  if (key || data || execCount) {
    doc.$unset['data.output'] = 1;
  }
  // 获取测试用例修改的字段
  let valueKey;
  let originalKey;
  let newKey;
  const oldTypeName = originalTest.typeName;
  function getIndexRangeObj(includeKeys = ['__indexRange'], sourceObj = {}) {
    if (utility.isArrayType(sourceObj)) {
      sourceObj.forEach((item, index) => {
        if (utility.isArrayType(item) || utility.isObjectType(item)) {
          getIndexRangeObj(includeKeys, item);
          if (utility.isNullItem(item)) {
            sourceObj.splice(index, 1);
          }
        } else {
          sourceObj.splice(index, 1);
        }
      });
    } else if (utility.isObjectType(sourceObj)) {
      Object.keys(sourceObj).forEach((keyName) => {
        if (!utility.isNumberType(sourceObj[keyName]) && !utility.isStringType(sourceObj[keyName])) {
          getIndexRangeObj(includeKeys, sourceObj[keyName]);
        }
        if ((utility.isObjectType(sourceObj[keyName]) || utility.isArrayType(sourceObj[keyName])) && utility.isNullItem(sourceObj[keyName])) {
          delete sourceObj[keyName];
        } else if ((utility.isNumberType(sourceObj[keyName]) || utility.isStringType(sourceObj[keyName])) && !includeKeys.includes(keyName)) {
          delete sourceObj[keyName];
        }
      });
    }
    return sourceObj;
  }
  return new Promise((resolve, reject) => {
    testId = mongoose.Types.ObjectId(testId);
    let addStub = false;
    TestCase.updateOne({ _id: testId, kind: { $ne: testKind.modified } }, doc)
      .then(async () => {
        let preOperator = Promise.resolve();
        const doc = { $set: { taskId: null, kind: testKind.modified, hash: utility.shortUUID() }, $unset: {} };
        if (data) {
          doc.$set.data = data;
        }
        if (description || description === '') {
          doc.$set.description = description;
        }
        // 重复执行次数
        if (execCount) {
          doc.$set.execCount = execCount;
        }
        if (requirementInfo !== undefined) {
          doc.$set.requirementInfo = requirementInfo;
        }
        if (typeName && typeName !== oldTypeName) {
          doc.$set.typeName = typeName;
          doc.$set.typeNumber = (await numbers.next(numbers.getTestcaseTypeNumberName(originalTest.functionId, typeName))).number;
        }
        if (testMethod) {
          doc.$set.testMethod = testMethod;
        }
        if (userDefinedFields && Array.isArray(userDefinedFields)) {
          preOperator = preOperator.then(() => {
            doc.$set.userDefinedFields = [];
            const udfIds = userDefinedFields.map((u) => u.key).map(mongoose.Types.ObjectId);
            return UserDefinedFields.find({ _id: { $in: udfIds } })
              .then((udfObjs) => {
                userDefinedFields.filter(({ value }) => value !== null) // 过滤掉null的
                  .forEach(({ key, value }) => {
                    if (!key || typeof value !== 'string') {
                      throw new PhoenixError('invalid request', 'malformed input');
                    }
                    const defined = udfObjs.find((o) => o._id.equals(key));
                    if (!defined) {
                      throw new PhoenixError('invalid request', `user defined field ${key} not found`);
                    }
                    if (defined.scope !== 'testcase') {
                      throw new PhoenixError('invalid request', `user defined field ${key} scope error: ${defined.scope}`);
                    }
                    doc.$set.userDefinedFields.push({
                      key: defined._id,
                      value,
                      name: defined.name,
                    });
                  });
              });
          });
        }
        if (key) {
          if (isStubBodyKey(key)) { // 代码打桩
            const stubName = key.split('.')[1];
            doc.$set[`data.stubs.${stubName}.body`] = value.code;
            doc.$set[`data.stubs.${stubName}.kind`] = stubKind.code;
            doc.$set[`data.stubs.${stubName}.times`] = value.times || 0;
            doc.$unset[`data.stubs.${stubName}.@value`] = 1;
            doc.$unset[`data.output.stubs.${stubName}`] = 1;
          } else if (isStubTimeKey(key)) { // 赋值打桩
            const stubName = key.split('.')[1];
            doc.$set[`data.stubs.${stubName}.times`] = value || 0;
            doc.$set[`data.stubs.${stubName}.kind`] = stubKind.value;
            doc.$unset[`data.stubs.${stubName}.body`] = 1;
            // 桩函数变参处理
            if (originalTest.data
              && originalTest.data.stubs
              && originalTest.data.stubs[stubName]
              && originalTest.data.stubs[stubName]['@value']
            ) {
              Object.keys(originalTest.data.stubs[stubName]['@value']).forEach((times) => {
                if (Number(times) > value) {
                  doc.$unset[`data.stubs.${stubName}.@value.${times}`] = 1;
                }
              });
              // 桩函数新加调用次数同步数组变量展开个数
              if (originalTest.data.stubs[stubName].times < value) {
                const timeObj = originalTest.data.stubs[stubName]['@value'][0] || {};
                const nextTimes = originalTest.data.stubs[stubName].times + 1;
                // 获取原始的__indexRange 结构
                const __indexRangeObj = getIndexRangeObj(['__indexRange'], JSON.parse(JSON.stringify(timeObj)));
                if (Object.keys(__indexRangeObj).length > 0) {
                  for (let i = nextTimes; i <= value; i += 1) {
                    doc.$set[`data.stubs.${stubName}.@value.${i}`] = __indexRangeObj;
                  }
                }
              }
            }
            // 桩函数参数检查项处理
            if (originalTest.data
              && originalTest.data.output
              && originalTest.data.output.stubs
              && originalTest.data.output.stubs[stubName]
              && originalTest.data.output.stubs[stubName]['@value']
            ) {
              Object.keys(originalTest.data.output.stubs[stubName]['@value']).forEach((times) => {
                if (Number(times) > value) {
                  doc.$unset[`data.output.stubs.${stubName}.@value.${times}`] = 1;
                }
              });
            }
          } else if (value) {
            doc.$set[`data.${key}`] = value;
          } else {
            doc.$unset[`data.${key}`] = 1;
          }

          if (isClassArrayInitKey(key)) {
            addStub = true;
            const [keyStr] = key.split('.#.');
            const unsetArr = { $unset: {} };
            unsetArr.$unset[`data.${keyStr}`] = 1;
            preOperator = preOperator.then(() => TestCase.updateOne({ _id: testId }, unsetArr));
          } else if (isClassInitKey(key)) {
            addStub = true;
            const unsetKey = key.split('.');
            unsetKey.pop();
            doc.$unset[`data.${unsetKey.join('.')}.@params`] = 1;
          }
        }
        if (updateSourceInRegression) {
          doc.$set.sourceInRegression = 1;
        }
        if (addStub && value) {
          doc.$set[`data.${key}`] = value.mangledId;
        }
        return preOperator
          .then(() => TestCase.findOneAndUpdate(
            { _id: testId },
            doc,
            { new: true, rawResult: true }
          ))
          .then((testcase) => {
            const promises = [];
            if (key || data || execCount) {
              promises.push(TestCaseGroupModel.removeTestResultByTestId(
                testId,
                testcase.value.functionId
              ));
            }
            promises.push(codeDefects.setCodeDefectToManual(
              {
                functionId: testcase.value.functionId,
                testIds: [testId],
              }
            ));
            // 不属于测试用例上传导致的用例更新时，同步更新其他用例的需求关联信息
            if (requirementInfo && !testcaseImport) {
              promises.push(TestCase.updateMany(
                {
                  functionId: testcase.value.functionId,
                },
                {
                  $set: {
                    requirementInfo,
                  },
                }
              ));
            }
            if (typeName && typeName !== oldTypeName) {
              promises.push(resetTestTypeNumberSequentially(testcase.value.functionId, typeName).then(() => (oldTypeName
                ? resetTestTypeNumberSequentially(testcase.value.functionId, oldTypeName)
                : Promise.resolve())));
            }
            return Promise.all(promises);
          });
      })
      .then(async () => {
        if (key && key.indexOf('.') !== -1) {
          valueKey = key.substring(key.lastIndexOf('.') + 1);
        }
        if (valueKey === 'fileId') {
          const file = await File.findById(value);
          if (file) {
            originalKey = `data.${key.substring(0, key.lastIndexOf('.'))}`;
            // 同步输出检查项文件id
            let outputKey;
            if (originalKey.split('.').includes('variables')) {
              outputKey = `data.output${key.substring('variables'.length, key.lastIndexOf('.'))}`;
            }
            if (outputKey) {
              await TestCase.updateOne({
                _id: mongoose.Types.ObjectId(testId),
                [outputKey]: { $exists: true },
              }, {
                $set: {
                  [`${outputKey}.fileId`]: file._id.toString(),
                  [`${outputKey}.filePath`]: file.path,
                },
              });
            }
            await TestCase.updateOne({ _id: testId }, { $set: { [`${originalKey}.filePath`]: file.path } });
            const newkey = key.substring(key.lastIndexOf('.') + 1, key.length);
            if (handleIntegrationVariables.checkAttachFileIdOrFunctionId(newkey)) {
              const keyName = newkey.substring(handleIntegrationVariables.checkAttachFileIdOrFunctionId(newkey)[0], '');
              newKey = `${originalKey.substring(0, originalKey.lastIndexOf('.'))}.${keyName}@${value}`;
              await TestCase.updateOne({ _id: testId }, { $rename: { [originalKey]: newKey } });
            }
          }
        }
        return Promise.resolve();
      })
      .then(() => {
        if (!addStub) {
          return Promise.resolve();
        }
        return TestCase.findOne({ _id: testId })
          .then((testcase) => functions.addStubForConstructor(testcase.functionId, value.typeName, value.mangledId, value.fileId));
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 *  Convert data:
      {
        "output": {
          "malloc": {
            "target_p": {
              "@value": {
                "0": {
                  "@value": {
                    "x": { "@value": "1" },
                    "y": { "@value": "12", "@result": "14" }
                  }
                }
              }
            }
          },
          "%": { "@value": "1", "@result": "2" }
        }
      }
    To
      {
        "output": {
          "malloc": {
            "target_p": {
              "@value": {
                "0": {
                  "@value": {
                    "x": { "@value": "1" },
                    "y": { "@value": "14" }
                  }
                }
              }
            }
          },
          "%": { "@value": "2" }
        }
      }
 */
function copyResultToValue(data) {
  if (typeof data === 'object') {
    if (data['@result']) {
      data['@value'] = data['@result'];
      delete data['@result'];
    }
    return Object.fromEntries(Object.entries(data).map(([k, v]) => [k, copyResultToValue(v)]));
  }
  if (Array.isArray(data)) {
    return data.map(copyResultToValue);
  }
  return data;
}

/**
 * @param {String} testId 测试用例id
 */
async function resultCopy(testId, outputField) {
  const updateQuery = {
    $set: { taskId: null, kind: testKind.modified, 'data.output': copyResultToValue(outputField) },
  };
  const testcase = await TestCase.findOneAndUpdate(
    { _id: testId }, updateQuery, { new: true, rawResult: true, checkKeys: false }
  );
  return Promise.all([
    TestCaseGroupModel.removeTestResultByTestId(testId, testcase.value.functionId),
    codeDefects.setCodeDefectToManual({ functionId: testcase.value.functionId, testIds: [testId] }),
  ]);
}

/**
 * 分解获取测试用例中的值
 * @param {Object} data 数据
 * @param {String} key 键值
 * @param {String} field 当前字段
 * @return {Object}
 */
function getTestCaseValues(data, key, field = undefined) {
  const values = {};
  const valueArray = [{ field, data }];
  while (valueArray.length > 0) {
    const { field, data } = valueArray.pop();
    if (utility.isObjectType(data)) {
      Object.keys(data).forEach((name) => {
        const value = data[name];
        const childField = field ? `${field}.${name}` : name;
        if (name === key && (utility.isStringType(value) || utility.isNumberType(value))) {
          values[childField] = value;
        } else {
          valueArray.push({ field: childField, data: value });
        }
      });
    }
  }
  return values;
}

/**
 * 处理错误情况下的执行结果
 * @param {String} testId 测试用例id
 * @param {String} taskId 任务id
 * @param {Number} resultKind 结果类型
 * @param {String} message 异常结果时表示异常信息
 * @param {Object} location 位置信息
 * @param {Object} projectVersion 项目信息
 * @return {Promise<unknown>}
 */
function handleRuntimeErrorResult(testId, taskId, resultKind, message, location, projectVersion) {
  return new Promise((resolve, reject) => {
    let testCase;
    testId = mongoose.Types.ObjectId(testId);
    getTest(testId)
      .then((res) => {
        testCase = res;
        if (!res) {
          return Promise.resolve();
        }
        if (!testCase.data) {
          testCase.data = {};
        }
        const doc = { $set: {}, $unset: {} };
        const lastResult = getTestCaseValues(testCase.data.output, '@result', 'data.output');
        Object.keys(lastResult).forEach((field) => {
          doc.$unset[`${field.slice(0, -'result'.length)}result`] = 1;
        });
        return TestCase.updateOne({ _id: testId, taskId }, doc);
      }).then(() => {
        const versionIdArr = [projectVersion._id];
        if (projectVersion.environmentId && projectVersion.environmentId.length > 0) {
          versionIdArr.push(...projectVersion.environmentId);
        }
        if (location && location.file) {
          return File.find({
            versionId: { $in: versionIdArr },
            path: location.file,
            kind: fileModel.kind.file,
          }).lean();
        }
        return Promise.resolve();
      }).then((file) => {
        if (!file || file.length === 0) {
          return Promise.resolve();
        }
        let inProject = false;
        file.forEach((f) => {
          if (inProject) {
            return;
          }
          if (f.versionId.toString() === projectVersion._id.toString()) {
            inProject = true;
          }
          location.fileId = f._id;
        });
        return Promise.resolve();
      })
      .then(() => operationLogs.recordOperationLog({
        userId: projectVersion.creatorId,
        projectId: testCase.projectId,
        versionId: testCase.versionId,
        fileId: testCase.fileId,
        functionId: testCase.functionId,
        operationType: 'execute',
        operationObject: 'testcase',
        name: getTestIdentifier(testCase),
        testcaseId: testId,
        operationDesc: `${resultKind === testResultKind.runtimeError || resultKind === testResultKind.timeout ? '运行时错误' : '执行异常'}: ${message}`,
        operationResult: 2,
      }))
      .then(() => updateTestResult(testId, resultKind, message, location))
      .then(() => codeDefects.processAutoDefects(
        testCase.kind,
        { begin: location },
        {
          projectId: testCase.projectId,
          versionId: testCase.versionId,
          fileId: testCase.fileId,
          functionId: testCase.functionId,
          testId: testCase._id,
        }
      ))
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 更新测试用例输出值
 * @param {String} testId 测试用例id
 * @param {String} taskId 任务id
 * @param {Object} output 输出数据
 * @param {Object} projectVersion 版本信息
 * @return {Promise}
 */
function updateTestOutputResult(testId, taskId, output, projectVersion, error = false) {
  return new Promise((resolve, reject) => {
    testId = mongoose.Types.ObjectId(testId);
    let testCase;
    getTest(testId)
      .then((res) => {
        testCase = res;
        return FunctionVariable.findOne({ functionId: testCase.functionId }, { 'variables.global': 1 });
      })
      .then((res) => {
        if (!testCase || !res) {
          return Promise.resolve();
        }
        if (!testCase.data) {
          testCase.data = {};
        }
        // 将自定义全局变量的结果值从malloc中移动到global里
        if (res && res.variables && res.variables.global && output.malloc) {
          const globals = res.variables.global;
          Object.keys(output.malloc).forEach((key) => {
            if (!globals[key]) {
              return;
            }
            if (!globals[key].selfDefined) {
              return;
            }
            if (!output.global) {
              output.global = {};
            }
            output.global[key] = output.malloc[key];
            delete output.malloc[key];
          });
        }
        return getTestFieldConfig(testCase.functionId, projectVersion);
      })
      .then(() => {
        // testCase中的原有预期值
        const expectValues = getTestCaseValues(testCase.data.output, '@value', 'data.output');
        const expectResults = getTestCaseValues(testCase.data.output, '@result', 'data.output');
        // content中的实际值
        const resultValues = getTestCaseValues(output, '@value', 'data.output');
        // inRange在范围内的值
        const inRangeValues = getTestCaseValues(output, '@inRange', 'data.output');
        // 空值检查
        const nullCheckValues = getTestCaseValues(output, '@isChanged', 'data.output');
        // 桩函数参数检查项
        const matchedValues = getTestCaseValues(output, '@isMatch', 'data.output');
        const doc = { $set: {}, $unset: {} };
        if (!output.unused) {
          output.unused = {};
        }
        doc.$set['data.output.unused'] = output.unused;
        let resultKind = testResultKind.succeed;
        // 将桩函数参数检查项的所有实际值设置成？
        Object.keys(expectValues).forEach((field) => {
          if (!field.match(/^data\.output\.stubs|integratedFuncs\.[^.]+\.@value\..+\.@value$/)) {
            return;
          }
          if (!resultValues[field]
            && !matchedValues[`${field.slice(0, -'value'.length)}isMatch`]
            && expectValues[field].substr(0, 1) !== '@') {
            resultValues[field] = '?';
          }
        });
        Object.keys(resultValues).forEach((field) => {
          // 实际值进制转换
          // const numericalBasePath = field.slice(5);
          // if (testConfig.numericalBases[numericalBasePath]) {
          //   resultValues[field] = utility.toBase(resultValues[field], testConfig.numericalBases[numericalBasePath]);
          // }
          const fieldPath = field.slice(0, -'value'.length);
          if (expectValues[field]
            && expectValues[field] !== '?'
            && (inRangeValues[`${fieldPath}inRange`] === 0
              || (inRangeValues[`${fieldPath}inRange`] === undefined && !matchedValues[`${fieldPath}isMatch`]))
          ) {
            resultKind = testResultKind.failed;
            doc.$set[`${fieldPath}isEqual`] = false;
          }
          doc.$set[`${fieldPath}result`] = resultValues[field];
          // 如果自动填充实际值开启并且没有设置预期值，将实际值赋给预期值
          if (projectVersion.autoFill && expectValues[field] === undefined) {
            doc.$set[`${fieldPath}value`] = resultValues[field];
            expectValues[field] = resultValues[field];
          }
          // 存储空值检查
          if (nullCheckValues[`${fieldPath}isChanged`] === 1) {
            doc.$set[`${fieldPath}isChanged`] = true;
          }
          // 预期值填写'?'和不填预期值结果等价
          if (expectValues[field] && expectValues[field] === '?') {
            delete expectValues[field];
          }
        });
        Object.keys(expectResults).forEach((field) => {
          if (inRangeValues[`${field.slice(0, -'result'.length)}inRange`] || matchedValues[`${field.slice(0, -'result'.length)}isMatch`]) {
            doc.$unset[`${field.slice(0, -'result'.length)}isEqual`] = 1;
            doc.$unset[`${field.slice(0, -'result'.length)}isChanged`] = 1;
          }
        });
        if (resultKind === testResultKind.succeed && !Object.keys(expectValues).length && !projectVersion.autoFill) {
          // 如果一个预期值都没设置的话，如果执行成功，更新结果为无结果或者通过*(void函数设置autofilled除外)
          resultKind = projectVersion.strictOutputCheck === false
            ? testResultKind.nonStrictSucceed : testResultKind.noResult;
        }
        // 实际调用次数记录到testCase表
        let stubKeys = [];
        if (testCase.data && testCase.data.output && testCase.data.output.stubs) {
          stubKeys = Object.keys(testCase.data.output.stubs);
        }
        stubKeys.forEach((stubName) => {
          // 没有调用到的桩函数默认调用次数是0
          const count = output.stubs && output.stubs[stubName] ? (output.stubs[stubName]['@count'] || 0) : 0;
          doc.$set[`data.output.stubs.${stubName}.@count`] = count;
          // 桩函数调用次数导致执行结果变化特殊处理
          const expectedCount = utility.getValueByKeyPath(testCase.data.output, `stubs.${stubName}.@countExpected`);
          if (expectedCount !== undefined
            && (resultKind === testResultKind.succeed
              || resultKind === testResultKind.nonStrictSucceed
              || resultKind === testResultKind.noResult)
          ) {
            // 设置了桩函数调用次数预期值，执行结果要改为成功或失败，不能为无结果或者通过*
            resultKind = parseInt(expectedCount, 10) === count ? testResultKind.succeed : testResultKind.failed;
          }
        });
        if (error) {
          resultKind = testResultKind.timeout;
        }
        const success = (resultKind === testResultKind.succeed
          || resultKind === testResultKind.nonStrictSucceed
          || resultKind === testResultKind.noResult);
        const promises = [
          updateTestResult(testId, resultKind),
          TestCase.updateOne({ _id: testId, taskId }, doc),
          codeDefects.processAutoDefects(
            testCase.kind,
            testCase.location ? { begin: testCase.location } : [],
            {
              projectId: testCase.projectId,
              versionId: testCase.versionId,
              fileId: testCase.fileId,
              functionId: testCase.functionId,
              testId: testCase._id,
            }
          ),
          operationLogs.recordOperationLog({
            userId: projectVersion.creatorId,
            projectId: testCase.projectId,
            versionId: testCase.versionId,
            fileId: testCase.fileId,
            functionId: testCase.functionId,
            operationType: 'execute',
            operationObject: 'testcase',
            name: getTestIdentifier(testCase),
            testcaseId: testId,
            operationDesc: success ? '' : (error || '未通过: 预期值与实际值不一致'),
            operationResult: success ? 1 : 2,
          }),
        ];
        return Promise.all(promises);
      })
      .then(resolve)
      .catch(reject);
  });
}

function getTypeNumberString(functionName, typeName, typeNumber) {
  if (!typeName) {
    return '';
  }
  return `${typeName}_${functionName}_${`${typeNumber}`.padStart(3, '0')}`;
}

async function reshuffleTestNumber(functionId) {
  const numberName = numbers.getTestcaseNumberName(functionId);
  functionId = mongoose.Types.ObjectId(functionId);
  const testCase = await TestCase.findOne({ functionId }, { number: 1 }, { sort: { number: -1 } });
  await numbers.restore(numberName, testCase ? testCase.number : 0);
}

async function reshuffleTestTypeNumber(functionId, typeName) {
  if (!functionId || !typeName) {
    throw new Error('need functionId and typeName');
  }
  const numberName = numbers.getTestcaseTypeNumberName(functionId, typeName);
  functionId = mongoose.Types.ObjectId(functionId);
  const testCase = await TestCase.findOne({ functionId, typeName }, { typeNumber: 1 }, { sort: { typeNumber: -1 } });
  await numbers.restore(numberName, testCase ? testCase.typeNumber : 0);
}

async function resetTestTypeNumber(functionId, typeName) {
  if (!functionId || !typeName) {
    throw new Error('need functionId and typeName');
  }
  logger.verbose('resetTestTypeNumber', functionId, typeName);
  functionId = mongoose.Types.ObjectId(functionId);
  const func = await Func.findById(functionId, { functionName: 1 });
  if (!func) {
    throw new Error(`function id ${functionId} not found`);
  }
  const testCases = await TestCase.find({
    functionId,
    typeName,
  }, { typeNumber: 1, _id: 1 }, { sort: { typeNumber: 1 } });
  testCases.forEach((tc, idx) => {
    tc.typeNumber = idx + 1;
    tc.typeNumberString = getTypeNumberString(func.functionName, typeName, tc.typeNumber);
    return 0;
  });
  await TestCase.bulkSave(testCases);
  await reshuffleTestTypeNumber(functionId, typeName);
}

// avoid overlap
async function resetTestTypeNumberSequentially(functionId, typeName) {
  if (!functionId || !typeName) {
    throw new Error('need functionId and typeName');
  }
  const uniqueKey = numbers.getTestcaseTypeNumberName(functionId, typeName);
  return utility.deDuplicateRun(`resetTestTypeNumber_${uniqueKey}`, () => resetTestTypeNumber(functionId, typeName));
}

async function peekNextTypeNumberString(functionId, typeName) {
  if (!functionId || !typeName) {
    throw new Error('need functionId and typeName');
  }
  functionId = mongoose.Types.ObjectId(functionId);
  const func = await Func.findById(functionId, { functionName: 1 });
  if (!func) {
    throw new Error(`function id ${functionId} not found`);
  }
  const name = numbers.getTestcaseTypeNumberName(functionId, typeName);
  const next = 1 + await numbers.current(name);
  return getTypeNumberString(func.functionName, typeName, next);
}

/**
 * 添加测试用例
 * @param {String} projectId 项目id
 * @param {String} versionId 项目版本id
 * @param {String} fileId 文件id
 * @param {String} functionId 函数id
 * @param {Object} data 测试用例数据
 * @param {Number} kind 测试用例类型
 * @param {String} description 测试用例描述
 * @param {Object} location 缺陷位置
 * @param {Object} userDefinedFields 自定义字段
 * @param {String} requirementInfo 需求关联信息
 * @param {Number} execCount 用例执行次数
 * @param {String} groupId 所属编组id
 * @param {Number} identifierNumber 测试用例唯一标识
 * @param {String} copyId 复制源测试用例Id
 * @return {Promise}
 */
async function addTest(
  projectId,
  versionId,
  fileId,
  functionId,
  {
    data,
    kind = testKind.modified,
    description = '',
    execCount = undefined,
    requirementInfo = undefined,
    location = undefined,
    userDefinedFields = undefined,
    versionType = undefined,
    _id = undefined,
    sourceInRegression = 1,
    typeName = 'GN',
    testMethod,
    identifierNumber,
    copyId = undefined,
  },
  groupId = undefined,
) {
  if (typeName && !Object.values(testType).includes(typeName)) {
    throw new PhoenixError('invalid request', `test type must one of ${Object.values(testType)}`);
  }
  const doc = {
    projectId: mongoose.Types.ObjectId(projectId),
    versionId: mongoose.Types.ObjectId(versionId),
    fileId: mongoose.Types.ObjectId(fileId),
    functionId: mongoose.Types.ObjectId(functionId),
    kind,
    data,
    description,
    location,
    sourceInRegression,
    typeName,
    hash: utility.shortUUID(),
  };
  if (testMethod) {
    doc.testMethod = testMethod;
  }
  if (requirementInfo) {
    doc.requirementInfo = requirementInfo;
  }
  if (execCount) {
    doc.execCount = execCount;
  }
  if (_id) {
    doc.originalImportedId = mongoose.Types.ObjectId(_id);
  }
  if (userDefinedFields && versionType) {
    doc.userDefinedFields = [];
    const testCaseUserDefinedFields = await userDefinedFieldsModel.listUserDefinedField(versionType, 'testcase');
    userDefinedFields.forEach((userDefinedFieldsValue) => {
      testCaseUserDefinedFields.list.forEach((testCaseUserDefinedField) => {
        if (userDefinedFieldsValue.name === testCaseUserDefinedField.name) {
          doc.userDefinedFields.push({
            value: userDefinedFieldsValue.value,
            key: testCaseUserDefinedField._id,
            name: userDefinedFieldsValue.name,
          });
        }
      });
    });
  }
  const [number, typeNumber, func] = await Promise.all([
    numbers.next(numbers.getTestcaseNumberName(functionId)),
    numbers.next(numbers.getTestcaseTypeNumberName(functionId, typeName)),
    Func.findById(mongoose.Types.ObjectId(functionId)),
  ]);
  if (!identifierNumber) {
    identifierNumber = (await numbers.next(numbers.getTestcaseIdentifierNumberName(versionId))).number;
  }
  // 绝对地址表达式半角点处理
  if (doc.data && doc.data.fixedAddrs && doc.data.fixedAddrs.exprs) {
    Object.keys(doc.data.fixedAddrs.exprs).forEach((expr) => {
      const newExpr = utility.toFullWidth(expr);
      if (expr !== newExpr) {
        doc.data.fixedAddrs.exprs[newExpr] = doc.data.fixedAddrs.exprs[expr];
        delete doc.data.fixedAddrs.exprs[expr];
      }
    });
  }
  doc.number = number.number;
  doc.identifierNumber = identifierNumber;
  doc.typeNumber = typeNumber.number;
  doc.typeNumberString = getTypeNumberString(func.functionName, typeName, typeNumber.number);
  const testCase = new TestCase(doc);
  const res = await testCase.save();
  const testId = res._id;
  doc.testId = testId; // eslint-disable-line no-underscore-dangle
  // 修改函数测试用例添加状态
  await Func.updateOne({ _id: mongoose.Types.ObjectId(functionId) }, {
    $set: {
      testCaseCreate: true,
    },
  });
  // 测试用例所属编组
  if (groupId) {
    await TestCaseGroup.findOneAndUpdate(
      { _id: mongoose.Types.ObjectId(groupId) },
      { $push: { children: { id: testId, childType: TestCaseGroupModel.type.testCase } } },
      { new: true },
    );
    await TestCaseGroupModel.removeTestResultByTestId(testId, functionId);
  }
  await autoAddCaseId(testId);
  if (copyId) {
    const promises = [];
    (await BreakPoint.find({ testcaseId: copyId }).lean()).forEach((breakPoint) => {
      delete breakPoint._id;
      delete breakPoint.__v;
      delete breakPoint.expandedVariables;
      promises.push(breakPointModel.upsertBreakPoint(null, { ...breakPoint, testcaseId: testId }, versionType));
    });
    await Promise.all(promises);
  }
  publisher.publishTestCaseMessage('add', doc);
  return res;
}

/**
 * 添加默认值，绝对地址表达式、桩类型、桩调用次数
 * @param {Object} testCase 测试用例数据
 * @param {Object} functionVariables 函数变量信息
 */
function addDefaultValue(testCase, functionVariables) {
  if (functionVariables.fixedAddrs && functionVariables.fixedAddrs.exprs) {
    if (!testCase.data.fixedAddrs) {
      testCase.data.fixedAddrs = {};
    }
    if (!testCase.data.fixedAddrs.exprs) {
      testCase.data.fixedAddrs.exprs = {};
    }
    Object.keys(functionVariables.fixedAddrs.exprs).forEach((expr) => {
      if (!functionVariables.fixedAddrs.exprs[expr].enabled) {
        return;
      }
      // 处理集成测试fileId
      const { fileId, filePath } = functionVariables.fixedAddrs.exprs[expr];
      // eslint-disable-next-line no-use-before-define
      const value = getDefaultFixedAddressValue(expr, functionVariables);
      if (!value) {
        return;
      }
      if (!testCase.data.fixedAddrs.exprs[expr]) {
        testCase.data.fixedAddrs.exprs[expr] = {};
      }
      if (!testCase.data.fixedAddrs.exprs[expr]['@value']) {
        testCase.data.fixedAddrs.exprs[expr]['@value'] = value;
        if (fileId) {
          testCase.data.fixedAddrs.exprs[expr].fileId = fileId;
        }
        if (filePath) {
          testCase.data.fixedAddrs.exprs[expr].filePath = filePath;
        }
      }
    });
  }

  if (functionVariables.stubs) {
    if (!testCase.data.stubs) {
      testCase.data.stubs = {};
    }
    Object.keys(functionVariables.stubs).forEach((stubName) => {
      const {
        defaultTimes, defaultKind,
      } = functionVariables.stubs[stubName];
      if (!testCase.data.stubs[stubName]) {
        testCase.data.stubs[stubName] = {};
      }
      if (!testCase.data.stubs[stubName].times) {
        testCase.data.stubs[stubName].times = defaultTimes || 0;
      }
      if (!testCase.data.stubs[stubName].kind) {
        testCase.data.stubs[stubName].kind = defaultKind || stubKind.value;
      }
      const { kind } = testCase.data.stubs[stubName];
      if (kind === stubKind.code && !testCase.data.stubs[stubName].body) {
        testCase.data.stubs[stubName].body = '';
      }
    });
  }
  return testCase;
}

async function moveTestCases(functionId, testIds, targetGroupId) {
  if (!testIds || !testIds.length) {
    throw new Error('need test case ids');
  }
  functionId = mongoose.Types.ObjectId(functionId);
  testIds = testIds.map(mongoose.Types.ObjectId);
  const idSet = new Set(testIds.map((id) => id.toString()));
  const testCases = await TestCase.find({ _id: { $in: testIds }, functionId });
  if (testCases.length < testIds.length) {
    throw new PhoenixError('not found', 'test case not found');
  }
  const srcGroups = await TestCaseGroup.find({ 'children.id': { $in: testIds } });

  if (!targetGroupId) {
    // 如果是移到最外层，则先清空用例执行结果，后续editTestCaseGroup过程中重新计算覆盖率才是正确的结果
    for (const tc of testCases) {
      // eslint-disable-next-line no-await-in-loop
      await removeResultOfExecution(tc._id, tc);
    }
  }
  for (const group of srcGroups) { // 更新原始编组
    const children = group.children.filter((tc) => !idSet.has(tc.id.toString()));
    await testCaseGroups.editTestCaseGroup({ children }, group._id);
    if (children.length === 0) {
      await TestCaseGroup.deleteOne({ _id: group._id });
      if (group.path.length === 1) {
        await TestCaseGroupModel.removeGroupCoverage(group._id, group.functionId);
      }
      await TestCaseGroupModel.clearEmptyTestGroups(group._id, 'group', functionId);
    }
  }
  if (targetGroupId) { // 更新目标编组
    const targetGroup = await TestCaseGroup.findOne({
      _id: mongoose.Types.ObjectId((targetGroupId)),
      functionId,
    });
    if (!targetGroup) {
      throw new PhoenixError('not found', 'targetGroup not found');
    }
    await testCaseGroups.editTestCaseGroup({
      children: [
        ...targetGroup.children,
        ...testCases.map((tc) => ({
          id: tc._id,
          childType: 'testcase',
        })),
      ],
    }, targetGroup._id);
  }
}

/**
 * 检查测试用例各项数据类型是否完整
 * @param {Object} testCase 测试用例
 * @param {Object} functionVariable 函数变量信息
 * @param {String} fileTypeSystem 文件类型系统
 * @param {String} versionType 版本类型
 * @returns {boolean}
 */
// eslint-disable-next-line no-unused-vars
function checkTestCaseType(testCase, functionVariable, fileTypeSystem,
  versionType = projectVersions.versionType.unitTest) {
  const testCaseCopy = JSON.parse(JSON.stringify(testCase));
  const functionVariableCopy = JSON.parse(JSON.stringify(functionVariable));
  let typeChecker;
  if (versionType === projectVersions.versionType.unitTest) {
    typeChecker = new TestCaseTransformer(testCaseCopy, functionVariableCopy, fileTypeSystem);
  } else {
    typeChecker = new IntegrationTestCaseTransformer(
      testCaseCopy, functionVariableCopy, fileTypeSystem
    );
  }
  typeChecker.transform();
  return typeChecker.isLegal;
}
/**
 * 解析慧程测试用例excel表格
 * @param {String} funcFilePath 函数表格文件路径
 * @returns
 */
function readTestCasesByHuiCheng(funcFilePath) {
  return new Promise((resolve, reject) => {
    const excelInstance = new ExcelModel();
    excelInstance.readFile(funcFilePath)
      .then((data) => {
        const funcTestCases = {};
        // 获取测试用例信息列号
        const testCaseInfoColNumber = data[0].columns.length;
        // 第一行是表头
        for (let i = 1; i < data.length; i += 1) {
          const rowData = data[i];
          if (rowData.columns && rowData.columns.length > 0) {
            const colData = rowData.columns.filter(
              (col) => col.colNumber === testCaseInfoColNumber
            );
            if (colData.length > 0) {
              const funcData = JSON.parse(colData[0].value);
              const requirements = rowData.columns.filter((col) => col.colNumber === 2);
              const descriptions = rowData.columns.filter((col) => col.colNumber === 4);
              const mangledId = (Object.keys(funcData))[0];
              const funcTestCaseData = funcData[mangledId];
              let testCase;
              let testCaseGroups = [];
              let functionVariables;
              // 使用最新的格式
              if (!utility.isObjectType(funcTestCaseData)) {
                return;
              }
              if (funcTestCaseData.testCases
                && funcTestCaseData.testCaseGroups
                && funcTestCaseData.functionVariables) {
                [testCase] = funcTestCaseData.testCases;
                testCaseGroups = funcTestCaseData.testCaseGroups;
                functionVariables = funcTestCaseData.functionVariables;
              }
              if (requirements.length > 0) {
                testCase.requirementInfo = requirements[0].value;
              }
              if (descriptions.length > 0) {
                testCase.description = descriptions[0].value;
              }
              // 测试用例数据
              if (!funcTestCases[mangledId]) {
                funcTestCases[mangledId] = {
                  testCases: [testCase],
                  testCaseGroups,
                  functionVariables,
                };
              } else {
                funcTestCases[mangledId].testCases.push(testCase);
              }
            }
          }
        }
        resolve(funcTestCases);
      }).catch(reject);
  });
}

/**
 * 更新已经导入的测试用例编组
 * @param {array[object]} groups 需要更新的编组
 * @param {array[object]} newGroups  新导入成功的编组
 */
async function updateImportedGroups(groups, newGroups) {
  const promises = [];
  groups.forEach((group) => {
    if (group.ableImport) {
      const newChildren = [];
      const newPath = [];
      group.children.forEach((child) => {
        // 替换编组内心的编组id
        if (child.childType === TestCaseGroupModel.type.testCaseGroup) {
          newGroups.forEach((newGroup) => {
            if (newGroup.originId === child.id) {
              newChildren.push({
                id: newGroup.newId,
                childType: TestCaseGroupModel.type.testCaseGroup,
              });
              group.ableImport = true;
            }
          });
        } else {
          newChildren.push(child);
        }
      });
      group.path.forEach((groupId) => {
        newGroups.forEach((newGroup) => {
          if (newGroup.originId === groupId) {
            newPath.push(newGroup.newId);
          }
        });
      });
      group.path = newPath;
      promises.push(TestCaseGroupModel.editTestCaseGroup(
        { path: newPath, children: newChildren }, group.newGroupId
      ));
    }
  });
  return Promise.all(promises);
}
/**
 * 导入函数相关的编组
 * @param {object} func 当前函数
 * @param {array[object]} groups 需要导入的编组
 * @param {array[object]} newTestCases 成功导入的测试用例
 */
async function importGroupsByfunction(func, groups, newTestCases) {
  const promises = [];
  const newGroups = [];
  const {
    projectId, versionId, fileId, _id: functionId,
  } = func;
  groups.forEach((group) => {
    const newChildren = [];
    group.children.forEach((child) => {
      // 替换编组内心的测试用例id
      if (child.childType === TestCaseGroupModel.type.testCase) {
        newTestCases.forEach((newTestCase) => {
          if (newTestCase.originId === child.id) {
            newChildren.push({
              id: newTestCase.newId,
              childType: TestCaseGroupModel.type.testCase,
            });
            group.ableImport = true;
          }
        });
      } else {
        newChildren.push(child);
      }
    });
    group.children = newChildren;
    if (group.ableImport) {
      promises.push((async () => {
        const { groupId } = await TestCaseGroupModel.addGroup({
          projectId,
          versionId,
          fileId,
          functionId,
          description: group.description,
          addDefaultTestCase: false,
        });
        group.newGroupId = groupId;
        newGroups.push({
          originId: group._id,
          newId: groupId.toString(),
        });
      })());
    }
  });
  await Promise.all(promises);
  if (newGroups.length > 0) {
    await updateImportedGroups(groups, newGroups);
  }
}

/**
 * 替换生成的测试用例
 * @param {Object} func 函数信息
 * @param {Object} functionVariables 函数变量信息
 * @param {Object} tests 测试用例信息
 * @return {Promise}
 */
function replaceGeneratedTests(func, functionVariables, tests) {
  return new Promise((resolve, reject) => {
    const functionId = func._id; // eslint-disable-line no-underscore-dangle
    TestCase.find({ functionId, kind: { $ne: testKind.modified } })
      .then((res) => {
        if (!res) {
          res = [];
        }
        const promises = res.map((test) => {
          const testId = test._id; // eslint-disable-line no-underscore-dangle
          return () => removeTest(testId, test);
        });
        return utility.promiseChain(promises);
      })
      .then(() => reshuffleTestNumber(functionId))
      .then(() => {
        const promises = [];
        tests.forEach((item) => {
          const testMethod = [];
          const { kind } = item;
          if (kind & testKind.divideZero || kind & testKind.nullPointer || kind & testKind.outOfBounds || kind & testKind.timeout) {
            testMethod.push(TestMethod.Error_Guess);
          }
          if (kind & testKind.borders) {
            testMethod.push(TestMethod.Boundary_Analysis);
          }
          const info = {
            description: item.description,
            kind,
            data: item.testcase,
            location: item.location || undefined,
            typeName: item.kind === testKind.borders ? testType.bj : undefined, // 边界用例
            testMethod,
          };
          // 处理绝对地址半角点
          if (info.data && info.data.fixedAddrs && info.data.fixedAddrs.exprs) {
            Object.keys(info.data.fixedAddrs.exprs).forEach((expr) => {
              const newExpr = utility.toFullWidth(expr);
              if (expr !== newExpr) {
                info.data.fixedAddrs.exprs[newExpr] = info.data.fixedAddrs.exprs[expr];
                delete info.data.fixedAddrs.exprs[expr];
              }
            });
          }
          addDefaultValue(info, functionVariables);
          // 去除output的@value字段
          const fieldArray = [info.data.output];
          while (fieldArray.length > 0) {
            const field = fieldArray.pop();
            if (utility.isObjectType(field)) {
              Object.keys(field).forEach((key) => {
                const child = field[key];
                if (key === '@value' && utility.isStringType(child)) {
                  delete field[key];
                } else {
                  fieldArray.push(child);
                }
              });
            }
          }
          // 更新桩调用次数和设置参数属性
          if (info.data.stubs) {
            Object.keys(info.data.stubs).forEach((stubName) => {
              const stub = info.data.stubs[stubName];
              if (!stub['@value']) {
                return;
              }
              if (!stub.times) {
                stub.times = 0;
              }
              Object.keys(stub['@value']).forEach((index) => {
                const indexNumber = Number.parseInt(index.toString(), 10);
                if (stub.times < indexNumber) {
                  stub.times = indexNumber;
                }
                if (stub['@value'][index].params && functionVariables.stubs && functionVariables.stubs[stubName]) {
                  const fixedParamsLength = functionVariables.stubs[stubName].params ? functionVariables.stubs[stubName].params.length : 0;
                  Object.keys(stub['@value'][index].params).forEach((indexKey) => {
                    if (Number(indexKey) + 1 > fixedParamsLength) {
                      stub['@value'][index].params[indexKey].isVariadic = true;
                    }
                  });
                }
              });
            });
          }
          promises.push(addTest(func.projectId, func.versionId, func.fileId, functionId, info));
        });
        return Promise.all(promises);
      })
      .then(() => tasks.isAutoTestAndFilterTask(func.taskId))
      .then((isAutoTestAndFilterTask) => {
        // 自动补充测试用例在生成测试用例阶段不发送websocket
        if (isAutoTestAndFilterTask) {
          return resolve();
        }
        const data = {
          creatorId: func.creatorId,
          versionId: func.versionId,
          fileId: func.fileId,
          functionId,
        };
        publisher.publishFunctionMessage('generator-tests', data);
        return resolve();
      })
      .catch(reject);
  });
}

/**
 * 搜索并修改
 * @param {Object} obj 搜索并修改的对象
 * @param {String} targetString 目标字符串
 * @param {Function} match 匹配函数
 * @param {Function} modify 匹配后修改规则
 * @returns {}
 */
function searchAndModify(obj, targetString, match, modify) {
  Object.keys(obj).forEach((key) => {
    if (utility.isObjectType(obj[key])) {
      searchAndModify(obj[key], targetString, match, modify);
    } else if (key === '@value' && match(obj[key], targetString)) {
      modify(obj, key);
    }
  });
}

/**
 * 重命名测试用例中的绝对地址
 * @param {String} functionId 绝对地址Id
 * @param {String} fixedAddressName 绝对地址名
 * @param {String} newFixedAddressName 新绝对地址名
 * @return {Promise}
 */
function renameFixedAddress(functionId, fixedAddressName, newFixedAddressName) {
  return new Promise((resolve, reject) => {
    let tests;
    getTestsOfFunctions(functionId)
      .then((res) => {
        tests = res;
        const promises = tests.map((test) => {
          const testId = test._id; // eslint-disable-line no-underscore-dangle
          const { fixedAddrs } = test.data;
          if (!fixedAddrs || !fixedAddrs.bases || !fixedAddrs.bases[fixedAddressName]) {
            return Promise.resolve();
          }
          const base = fixedAddrs.bases[fixedAddressName];
          const doc = { $set: {}, $unset: {} };
          doc.$set[`data.fixedAddrs.bases.${newFixedAddressName}`] = base;
          doc.$unset[`data.fixedAddrs.bases.${fixedAddressName}`] = 1;
          return TestCase.updateOne({ _id: testId }, doc);
        });
        return Promise.all(promises);
      })
      .then(() => {
        const promises = tests.map((test) => {
          const testId = test._id; // eslint-disable-line no-underscore-dangle
          const { output } = test.data;
          if (!output || !output.fixedAddrs || !output.fixedAddrs[fixedAddressName]) {
            return Promise.resolve();
          }
          const base = output.fixedAddrs[fixedAddressName];
          const doc = { $set: {}, $unset: {} };
          doc.$set[`data.output.fixedAddrs.${newFixedAddressName}`] = base;
          doc.$unset[`data.output.fixedAddrs.${fixedAddressName}`] = 1;
          return TestCase.updateOne({ _id: testId }, doc);
        });
        return Promise.all(promises);
      })
      .then(() => findFixedAddrsExprs(functionId, utility.convertDecToHex(fixedAddressName)))
      .then((keys) => {
        const promises = [];
        keys.forEach((key) => {
          const doc = { $set: {}, $unset: {} };
          const { testId, expr } = key;
          doc.$set[`data.fixedAddrs.exprs.${expr}.@value`] = utility.convertDecToHex(newFixedAddressName);
          promises.push(TestCase.updateOne({ _id: mongoose.Types.ObjectId(testId) }, doc));
        });
        return Promise.all(promises);
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 清除测试用例结果数据字段
 * @param {Object} testCase 测试用例数据
 */
function removeResult(testCase, keyPath = '') {
  if (utility.isObjectType(testCase)) {
    // 对象
    delete testCase['@result'];
    delete testCase['@isEqual'];
    delete testCase['@isChanged'];
    const regList = [
      /^([^.]*\.)?fixedAddrs\.bases\.[^.]+\.pointerTargets\.[^.]+/,
      /^([^.]*\.)?stubs\.[^.]+\.@value\.(\d+)\.params\.[^.]+/,
      /^\.runtime\.[^.]+/,
    ];
    if (!regList.some((r) => r.test(keyPath))) {
      delete testCase['@type'];
      delete testCase['@elementType'];
      delete testCase['@length'];
    }
    Object.keys(testCase).forEach((key) => {
      removeResult(testCase[key], `${keyPath}.${key}`);
      // 去掉值为空对象，空数组，以及null或者undefined的字段
      if (utility.isObjectType(testCase[key])) {
        if (Object.keys(testCase[key]).length > 0) {
          return;
        }
      } else if (utility.isArrayType(testCase[key])) {
        if (testCase[key].length > 0) {
          return;
        }
      } else if (testCase[key] || testCase[key] === '') {
        return;
      }
      delete testCase[key];
    });
  } else if (utility.isArrayType(testCase)) {
    // 数组
    testCase.forEach((item) => {
      removeResult(item, keyPath);
    });
  }
}

function attachFilePath(key, filePath, allFuncMap) {
  if (!filePath) {
    return key;
  }
  let prefix = key;
  key.replace(/(.+)(@[0-9a-fA-F]{24})$/, (rs, $1) => {
    // 匹配到了
    const objectId = rs.substring(rs.lastIndexOf('@') + 1, rs.length);
    // 加上所属函数
    if (allFuncMap && allFuncMap.has(objectId)) {
      const func = allFuncMap.get(objectId);
      //  区分c函数和c++函数
      prefix = `${$1}@${functions.isCFunction(func) ? func.functionName : func.mangledId}`;
    } else {
      prefix = $1;
    }
  });
  return `${prefix}@${filePath}`;
}
function handleTestCaseFiles(testcase, allFileMap, allFuncMap) {
  if (testcase.data.__files && testcase.data.__files.size === 0) {
    return;
  }
  const filePathMap = new Map();
  allFileMap.forEach((file) => {
    filePathMap.set(file.path, file._id.toString());
  });
  const fileFuncsMap = new Map();
  fileFuncsMap.set(allFileMap.get(testcase.fileId.toString()).path, [testcase.functionId.toString()]);
  if (testcase.data.stubs) {
    Object.values((testcase.data.stubs)).forEach((stub) => {
      if (!stub.functionId || !stub.fileId) {
        return;
      }
      const filePath = allFileMap.get(stub.fileId.toString()).path;
      if (!fileFuncsMap.has(filePath)) {
        fileFuncsMap.set(filePath, []);
      }
      fileFuncsMap.get(filePath).push(stub.functionId.toString());
    });
  }
  testcase.data.__files.forEach((filePath) => {
    if (allFileMap.get(filePathMap.get(filePath)).status === fileModel.statusKind.compiled
      || allFileMap.get(filePathMap.get(filePath)).kind === fileModel.kind.fileOutsideProject
    ) {
      return;
    }
    testcase.data.__files.delete(filePath);
    if (!fileFuncsMap.has(filePath)) {
      return;
    }
    let frequentlyUsedPath = '';
    let maxUsedCount = 0;
    fileFuncsMap.get(filePath).forEach((funcId) => {
      if (!allFuncMap.has(funcId) || !allFuncMap.get(funcId).sourceParsedFile) {
        return;
      }
      const fileCountMap = new Map();
      const sourceParsedFile = [...allFuncMap.get(funcId).sourceParsedFile];
      for (let i = 0; i < sourceParsedFile.length; i += 1) {
        const filePath = allFileMap.get(sourceParsedFile[i].toString()).path;
        if (testcase.data.__files.has(filePath)) {
          break;
        }
        if (!fileCountMap.has(filePath)) {
          fileCountMap.set(filePath, 0);
        }
        fileCountMap.set(filePath, fileCountMap.get(filePath) + 1);
        if (fileCountMap.get(filePath) > maxUsedCount) {
          frequentlyUsedPath = filePath;
          maxUsedCount = fileCountMap.get(filePath);
        }
      }
    });
    if (frequentlyUsedPath) {
      testcase.data.__files = new Set([...testcase.data.__files, frequentlyUsedPath]);
    }
  });
}
/**
 * 614补全测试用例检查项通过准则
 * @param {Object} output 输出检查项
 * @param {Object} tolerance 通过准则
 * @param {Object} fileTypes  文件类型系统
 * @param {String} defaultFileId  默认的文件id/被测函数所在文件id
 */
function completetTolerance(output, tolerance = undefined, fileTypes, defaultFileId) {
  if (!tolerance || !output) {
    return;
  }
  function recursionObj(output) {
    if (utility.isObjectType(output)) {
      const typeName = output['@type'];
      // 兼容集成测试和单元测试取类型
      const types = typeName && output.fileId ? fileTypes[output.fileId] : fileTypes[defaultFileId];
      if (typeName
        && types
        && types[typeName]
        && types[typeName]['@attributes']
        && !output['@tolerance']
        && output['@value']) {
        const attributes = types[typeName]['@attributes'];
        if (tolerance['%'] && attributes.includes('isInteger')) {
          output['@tolerance'] = tolerance;
        }
        // 614只针对float类型
        if (tolerance['+'] && tolerance['-'] && attributes.includes('isFloat')) {
          output['@tolerance'] = tolerance;
        }
      }
      Object.keys(output).forEach((key) => recursionObj(output[key]));
    } else if (utility.isArrayType(output)) {
      output.forEach((a) => recursionObj(a));
    }
  }
  recursionObj(output);
}
/**
 * 补全测试用例数据
 * @param {Object} testCase 测试用例数据
 * @param {Object} functionVariable 函数变量信息
 * @param {Object} fileTypeSystem 文件类型系统
 * @param {Object} integration true集成测试测试用例
 * @param {Object} allFuncMap 所有函数
 * @param {Object} tolerance 通过准则
 * @param {String} language c/c++
 * @param {Object} numericalBases 测试用例进制转换
 */
function completeTest(
  testCase,
  functionVariable,
  fileTypeSystem,
  {
    integration = false,
    allFuncMap,
    tolerance,
    language,
    numericalBases,
    autoFill = false,
  } = {}
) {
  const Transformer = integration ? IntegrationTestCaseTransformer : TestCaseTransformer;
  const functionVariableCopy = JSON.parse(JSON.stringify(functionVariable));
  const typeChecker = new Transformer(
    {},
    functionVariableCopy,
    fileTypeSystem
  );
  if (!typeChecker.isLegal) {
    throw new Error(' variables typeChecker failed!');
  }
  // 检查项补全@type
  const integratedFuncs = testCase.data && testCase.data.output && testCase.data.output.integratedFuncs ? testCase.data.output.integratedFuncs : {};
  // 全局设置tolerance需要补全类型
  const completeTolerlanceType = tolerance && !tolerance['='];
  // 集成函数检查项需要补全类型
  const completeIntegratedFuncsType = Object.keys(integratedFuncs).length > 0;
  if (autoFill
    || (testCase.data && testCase.data.output && fileTypeSystem && (completeTolerlanceType || completeIntegratedFuncsType))
  ) {
    const newTestCaceOutput = {
      autoFill: true,
      arrayIndexRange: 1,
      data: {
        output: { ...testCase.data.output },
      },
    };
    if (testCase.data.integratedFuncs) {
      newTestCaceOutput.data.integratedFuncs = { ...testCase.data.integratedFuncs };
    }
    const testCaseTransformer = new Transformer(newTestCaceOutput, functionVariableCopy, fileTypeSystem);
    testCaseTransformer.transform();
    ['globalOutputData', 'mallocOutputData', 'staticsOutputData', 'stubsOutputData', 'returnValueData'].forEach((key) => {
      testCaseTransformer[key].forEach((item) => {
        utility.recursiveIter(item, 'children', (obj) => {
          if (!obj.children) {
            // 叶子结点
            const { fieldPath } = obj;
            if (!utility.getValueByKeyPath(testCaseTransformer.testCaseData.data, fieldPath)) {
              // console.log(fieldPath);
              utility.setValueByKeyPath(testCaseTransformer.testCaseData.data, fieldPath, '?');
            }
          }
        });
      });
    });
    if (testCaseTransformer.testCaseData
      && testCaseTransformer.testCaseData.data
      && testCaseTransformer.testCaseData.data.output) {
      const formatOutput = testCaseTransformer.testCaseData.data.output;
      // 补全每一个值的tolerance
      if (completeTolerlanceType) {
        const types = integration ? fileTypeSystem : ({ [fileTypeSystem.fileId.toString()]: fileTypeSystem.types });
        completetTolerance(formatOutput, tolerance, types, testCase.fileId.toString());
      }
      testCase.data.output = formatOutput;
    }
  }

  // 补全指针目标及类型
  const fields = ['@length', '@elementType', '@type'];
  testCase.data = testCase.data || {};
  testCase.data.hash = testCase.hash;
  if (functionVariable.malloc) {
    testCase.data.malloc = testCase.data.malloc || {};
    Object.keys(functionVariable.malloc).forEach((varName) => {
      const mallocVariable = functionVariable.malloc[varName];
      testCase.data.malloc[varName] = testCase.data.malloc[varName] || {};
      fields.forEach((field) => {
        if (mallocVariable[field]) {
          testCase.data.malloc[varName][field] = mallocVariable[field].toString();
        }
      });
      if (mallocVariable['@init']) {
        testCase.data.malloc[varName]['@init'] = utility.deepMerge(mallocVariable['@init'], testCase.data.malloc[varName]['@init']);
      }
      if (functionVariable.output
        && functionVariable.output.malloc
        && functionVariable.output.malloc[varName]) {
        fields.forEach((field) => {
          if (!functionVariable.output.malloc[varName][field]) {
            delete functionVariable.output.malloc[varName][field];
          }
        });
      }
    });
  }

  if (testCase.data.malloc) {
    Object.keys(testCase.data.malloc).forEach((mallocName) => {
      if (!hasValue(testCase.data.malloc[mallocName]['@init'])) {
        delete testCase.data.malloc[mallocName]['@init'];
      }
    });
  }
  // 补全桩函数定义信息
  if (functionVariable.stubs) {
    testCase.data.stubs = testCase.data.stubs || {};
    Object.keys(functionVariable.stubs).forEach((stubName) => {
      const stubs = functionVariable.stubs[stubName];
      testCase.data.stubs[stubName] = testCase.data.stubs[stubName] || {};
      testCase.data.stubs[stubName]['@value'] = testCase.data.stubs[stubName]['@value'] || {};
      testCase.data.stubs[stubName]['@type'] = testCase.data.stubs[stubName]['@type'] || {};
      if (integration && !stubs.enableStub) {
        // 集成测试未打桩集成函数必须把@value删除，否则核心层判定为打桩
        delete testCase.data.stubs[stubName]['@value'];
      }
      Object.keys(stubs).forEach((key) => {
        testCase.data.stubs[stubName]['@type'][key] = testCase.data.stubs[stubName]['@type'][key] || stubs[key];
      });
    });
  }
  const variableStubKeys = Object.keys(functionVariable.stubs || {});
  // 补全桩内指针目标类型
  if (testCase.data.stubs) {
    Object.keys(testCase.data.stubs).forEach((stubName) => {
      if (!functionVariable.stubs || !variableStubKeys.includes(stubName)) {
        delete testCase.data.stubs[stubName];
        if (testCase.data.output && testCase.data.output.stubs && testCase.data.output.stubs[stubName]) {
          delete testCase.data.output.stubs[stubName];
        }
        logger.error(`can not find stub(${stubName}) in function variables of ${functionVariable.functionId}`);
        return;
      }
      if (!functionVariable.stubs[stubName].pointerTargets) {
        functionVariable.stubs[stubName].pointerTargets = {};
      }
      const stub = testCase.data.stubs[stubName] || {};
      if (stub['@value']) {
        Object.keys(stub['@value']).forEach((index) => {
          if (!stub['@value'][index].pointerTargets) {
            stub['@value'][index].pointerTargets = {};
          }
          const stubIndexValue = stub['@value'][index];
          const { pointerTargets } = stubIndexValue;
          const usedPointerTargets = [];
          ['%', 'params'].forEach((key) => {
            if (!stubIndexValue[key]) {
              return;
            }
            const fieldValues = getTestCaseValues(stubIndexValue[key], '@value');
            Object.keys(fieldValues).forEach((fieldKey) => {
              const matchValues = fieldValues[fieldKey].match(/^@(.*)/);
              if (matchValues && matchValues.length === 2) {
                usedPointerTargets.push(matchValues[1]);
              }
            });
          });
          Object.keys(functionVariable.stubs[stubName].pointerTargets).forEach((name) => {
            fields.forEach((field) => {
              const pointerTargetVariable = functionVariable.stubs[stubName].pointerTargets[name];
              if (!pointerTargetVariable || !pointerTargetVariable[field]) {
                return;
              }
              if (!usedPointerTargets.includes(name)) {
                delete pointerTargets[name];
                return;
              }
              if (!pointerTargets[name]) {
                pointerTargets[name] = {};
              }
              pointerTargets[name][field] = pointerTargetVariable[field].toString();
            });
          });
          if (utility.isEmpty(pointerTargets)) {
            delete stubIndexValue.pointerTargets;
          }
        });
      }
    });
  }
  // 补全绝对地址bases
  if (testCase.data.fixedAddrs && testCase.data.fixedAddrs.bases) {
    Object.keys(testCase.data.fixedAddrs.bases).forEach((baseFixedAddr) => {
      const { pointerTargets } = testCase.data.fixedAddrs.bases[baseFixedAddr];
      if (!pointerTargets) {
        return;
      }
      Object.keys(pointerTargets).forEach((offset) => {
        fields.forEach((field) => {
          if (!functionVariable.fixedAddrs
            || !functionVariable.fixedAddrs.bases
            || !functionVariable.fixedAddrs.bases[baseFixedAddr]) {
            logger.error(`can not find fixedAddrs(${baseFixedAddr} in function variables of ${functionVariable.functionId}`);
            return;
          }
          if (!functionVariable.fixedAddrs.bases[baseFixedAddr].pointerTargets) {
            return;
          }
          const value = functionVariable.fixedAddrs.bases[baseFixedAddr].pointerTargets[offset];
          if (!value || !value[field]) {
            return;
          }
          // 逻辑修改：绝对地址偏移量下的字段，testcase不存在的字段才去补全，否则以testcase自身为准
          if (!Object.hasOwnProperty.call(pointerTargets[offset], field)) {
            pointerTargets[offset][field] = value[field].toString();
          }
        });
        // 去掉pointerTargets偏移量数据中不必要的字段，防止核心类型识别错误，例如int类型不能有length/elementType
        if (pointerTargets[offset]['@type']) {
          const typeName = pointerTargets[offset]['@type'];
          let typeSystemType = null;
          if (!integration) {
            typeSystemType = typeChecker.getUnqualifiedType(typeName);
          } else if (functionVariable.fixedAddrs
            && functionVariable.fixedAddrs.bases
            && functionVariable.fixedAddrs.bases[baseFixedAddr]) {
            typeSystemType = typeChecker.getUnqualifiedTypeWithFieId(typeName, functionVariable.fixedAddrs.bases[baseFixedAddr].fileId.toString());
          }
          if (!typeSystemType) {
            logger.error(`type ${typeName} not found in type system data`);
            return;
          }
          ['@length', '@elementType'].forEach((field) => {
            if (!Object.hasOwnProperty.call(typeSystemType, field)) {
              delete pointerTargets[offset][field];
            }
          });
        }
      });
    });
  }
  // 补全绝对地址exprs
  if (testCase.data.fixedAddrs
    && testCase.data.fixedAddrs.exprs
    && functionVariable.fixedAddrs
    && functionVariable.fixedAddrs.exprs
  ) {
    testCase.data.fixedAddrs.bases = testCase.data.fixedAddrs.bases || {};
    testCase.data.fixedAddrs.exprs = JSON.parse(utility.toHalfWidth(JSON.stringify(testCase.data.fixedAddrs.exprs)));
    functionVariable.fixedAddrs.exprs = JSON.parse(utility.toHalfWidth(JSON.stringify(functionVariable.fixedAddrs.exprs)));
    Object.keys(functionVariable.fixedAddrs.exprs).forEach((expr) => {
      // eslint-disable-next-line no-use-before-define
      const defaultValue = getDefaultFixedAddressValue(expr, functionVariable);
      if (!defaultValue) {
        return;
      }
      // 集成测试补全测试用例中没有填值的base
      const baseKey = utility.convertHexToDec(defaultValue);
      if (functionVariable.fixedAddrs.bases
        && functionVariable.fixedAddrs.bases[baseKey]
        && !testCase.data.fixedAddrs.bases[baseKey]) {
        testCase.data.fixedAddrs.bases[baseKey] = {
          pointerTargets: functionVariable.fixedAddrs.bases[baseKey].pointerTargets || {},
        };
      }
      testCase.data.fixedAddrs.exprs[expr] = testCase.data.fixedAddrs.exprs[expr] || { '@value': defaultValue };
      // 集成测试补全绝对地址表达式的locations
      const { locations } = functionVariable.fixedAddrs.exprs[expr];
      if (integration && locations) {
        testCase.data.fixedAddrs.exprs[expr].locations = [];
        Object.keys(locations).forEach((index) => {
          testCase.data.fixedAddrs.exprs[expr].locations.push(index);
        });
      }
    });
  }
  // 清除掉输出检查项fixedAddrs检查项空的bases
  if (testCase.data && testCase.data.output && testCase.data.output.fixedAddrs) {
    utility.removeNullItem(testCase.data.output.fixedAddrs);
  }
  // 清除掉输入fixedAddrs空的bases
  if (testCase.data && testCase.data.fixedAddrs) {
    utility.removeNullItem(testCase.data.fixedAddrs);
  }
  // 补全预期值
  if (testCase.data.output) {
    delete testCase.data.output.unused;
  }
  utility.removeObjectBykey(functionVariable.output, ['@init', '@value']);
  utility.removeObjectBykey(testCase.data.output || {}, ['@init']);
  testCase.data.output = utility.deepMerge(functionVariable.output, testCase.data.output || {});
  if (!testCase.data.output) {
    testCase.data.output = {};
  }
  delete testCase.data.output.unused;
  if (Object.keys(testCase.data.output).length === 0) {
    testCase.data.output.global = {};
  }
  // 新加执行次数
  testCase.data.execCount = testCase.execCount || 1;

  // 死循环调用控制
  if (functionVariable.infiniteLoops) {
    testCase.data.loopLimit = {};
    functionVariable.infiniteLoops.forEach((loop) => {
      const key = `${loop.functionKey}@${loop.filePath}`;
      if (!testCase.data.loopLimit[key]) {
        testCase.data.loopLimit[key] = {};
      }
      testCase.data.loopLimit[key][loop.nodeId] = loop.times.toString();
    });
  }
  // 回填变量进制转换数据@base
  if (numericalBases) {
    Object.keys(numericalBases).forEach((key) => {
      utility.setValueByKeyPath(testCase.data, `${key.substring(0, key.lastIndexOf('@value'))}@base`, numericalBases[key].toString());
    });
  }
  // 拼接filePath和补全variable未定义的全局变量
  if (integration) {
    // 补全isNotDefined的全局变量
    if (functionVariable.variables && functionVariable.variables.global) {
      const globalFields = ['@length', '@elementType', '@type'];
      const variableGlobal = functionVariable.variables.global;
      Object.keys(variableGlobal).forEach((globalName) => {
        testCase.data.variables = testCase.data.variables || {};
        testCase.data.variables.global = testCase.data.variables.global || {};
        if (!testCase.data.variables.global[globalName]) {
          testCase.data.variables.global[globalName] = {};
          globalFields.forEach((field) => {
            if (variableGlobal[globalName][field]) {
              testCase.data.variables.global[globalName][field] = variableGlobal[globalName][field];
            }
          });
        }
      });
    }
    // 去掉测试用例中冗余集成函数的检查项数据和补全文件信息
    if (testCase.data.output.integratedFuncs) {
      const { integratedFuncs, output } = functionVariable;
      if (!integratedFuncs || !output.integratedFuncs) {
        delete testCase.data.output.integratedFuncs;
      }
      const testCaseOutputIntegratedFuncs = testCase.data.output.integratedFuncs;
      Object.keys(testCaseOutputIntegratedFuncs).forEach((mangled) => {
        if (!integratedFuncs[mangled] || !output.integratedFuncs[mangled]) {
          delete testCaseOutputIntegratedFuncs[mangled];
          return;
        }
        const { fileId, filePath } = integratedFuncs[mangled];
        if (!fileId || !filePath) {
          delete testCaseOutputIntegratedFuncs[mangled];
          return;
        }
        testCaseOutputIntegratedFuncs[mangled].fileId = fileId;
        testCaseOutputIntegratedFuncs[mangled].filePath = filePath;
        if (integratedFuncs[mangled]['@name']) {
          testCaseOutputIntegratedFuncs[mangled]['@name'] = integratedFuncs[mangled]['@name'];
        }
        utility.removeNullItem(testCaseOutputIntegratedFuncs[mangled]);
      });
    }
    const keyPaths = [
      // 输入
      'stubs',
      'malloc',
      'fixedAddrs.bases',
      'fixedAddrs.exprs',
      'variables.global',
      'variables.statics',
      // 输出
      'output.global',
      'output.statics',
      'output.malloc',
      'output.fixedAddrs',
      'output.integratedFuncs',
      'output.stubs',
    ];
    const filesSet = new Set();
    keyPaths.forEach((keyPath) => {
      // 注意：getValueByKeyPath仅支持plain object
      const testData = utility.getValueByKeyPath(testCase.data, keyPath);
      const funcData = utility.getValueByKeyPath(functionVariable, keyPath);
      if (!funcData) {
        utility.deleteKeyPath(testCase.data, keyPath);
      }
      if (testData && funcData) {
        Object.keys(testData).forEach((name) => {
          if (!funcData[name]) {
            logger.error(`can not find ${keyPath}.${name} in function variables of ${functionVariable.functionId}`);
            delete testData[name];
            return;
          }
          const filePath = testData[name].filePath || funcData[name].filePath || '';
          // 桩函数补充函数id和文件id到测试用例为去除未解析文件做准备
          if (keyPath === 'stubs') {
            const fileId = testData[name].fileId || funcData[name].fileId || '';
            const funcId = funcData[name].functionId || '';
            testData[name].fileId = fileId;
            testData[name].functionId = funcId;
          }
          if (filePath) {
            filesSet.add(filePath);
          }
          let newName;
          // 全局的绝对地址表达式只需要拼接文件路径
          if (keyPath === 'fixedAddrs.exprs'
            && funcData[name].functionId) {
            const expr = name.indexOf('@') === -1 ? `${name}@${funcData[name].functionId}` : name;
            newName = attachFilePath(expr, filePath, allFuncMap);
            testData[name]['@name'] = allFuncMap.has(funcData[name].functionId) ? allFuncMap.get(funcData[name].functionId).functionName : '';
          } else if (keyPath === 'output.integratedFuncs' && functions.isCFunction({ language })) { // c语言integratedFuncs需要使用：函数名@文件路径
            newName = attachFilePath(testData[name]['@name'], filePath, allFuncMap);
          } else {
            newName = attachFilePath(name, filePath, allFuncMap);
          }
          testData[newName] = testData[name];
          if (newName !== name) {
            delete testData[name];
          }
        });
      }
    });
    // stubs手动创建字段移到funcs字段
    if (testCase.data.stubs) {
      testCase.data.funcs = {};
      Object.keys(testCase.data.stubs).forEach((stubName) => {
        if (testCase.data.stubs[stubName]['@type']
          && testCase.data.stubs[stubName]['@type'].stubsTriggerKind === stubTriggerKind.manual) {
          testCase.data.funcs[stubName] = testCase.data.stubs[stubName];
          delete testCase.data.stubs[stubName];
        }
      });
    }
    // 将集成函数检查项放入到stubs中
    delete testCase.data.integratedFuncs;
    if (testCase.data.output && testCase.data.output.integratedFuncs) {
      testCase.data.output.stubs = testCase.data.output.stubs ? {
        ...testCase.data.output.stubs,
        ...testCase.data.output.integratedFuncs,
      } : testCase.data.output.integratedFuncs;
      delete testCase.data.output.integratedFuncs;
    }

    testCase.data.__files = filesSet;
  }
  // 处理手动添加的自定义全局变量
  if (functionVariable.variables && functionVariable.variables.global) {
    const variableGlobals = functionVariable.variables.global;
    const selfDefinedGlobals = new Map();
    Object.keys(variableGlobals).forEach((globalName) => {
      if (!variableGlobals[globalName].selfDefined) {
        return;
      }
      selfDefinedGlobals.set(globalName, variableGlobals[globalName]);
      // 将自定义的全局变量回填到测试用例中
      if (!testCase.data.malloc) {
        testCase.data.malloc = {};
      }
      testCase.data.malloc[globalName] = {};
    });
    if (selfDefinedGlobals.size > 0) {
      // 处理测试用例输入部分的自定义全局变量
      if (testCase.data && testCase.data.variables && testCase.data.variables.global) {
        const inputGlobals = testCase.data.variables.global;
        Object.keys(inputGlobals).forEach((globalName) => {
          if (!selfDefinedGlobals.has(globalName)) {
            return;
          }
          testCase.data.malloc[globalName] = {
            ...inputGlobals[globalName],
            ...selfDefinedGlobals.get(globalName),
          };
          delete inputGlobals[globalName];
        });
        if (Object.keys(inputGlobals).length === 0) {
          delete testCase.data.variables.global;
        }
      }
      // 处理测试用例输出检查项部分的自定义全局变量
      if (testCase.data && testCase.data.output && testCase.data.output.global) {
        const outputGlobals = testCase.data.output.global;
        Object.keys(outputGlobals).forEach((globalName) => {
          if (!selfDefinedGlobals.has(globalName)) {
            return;
          }
          if (!testCase.data.output.malloc) {
            testCase.data.output.malloc = {};
          }
          testCase.data.output.malloc[globalName] = outputGlobals[globalName];
          delete outputGlobals[globalName];
        });
        if (Object.keys(outputGlobals).length === 0) {
          delete testCase.data.output.global;
        }
      }
      // 处理测试用例输入部分桩函数内的自定义全局变量
      if (testCase.data && testCase.data.stubs) {
        const inputStubs = testCase.data.stubs;
        Object.keys(inputStubs).forEach((stubName) => {
          if (!inputStubs[stubName]['@value']) {
            return;
          }
          const stubTimesValues = inputStubs[stubName]['@value'];
          Object.keys(stubTimesValues).forEach((time) => {
            if (!stubTimesValues[time].global) {
              return;
            }
            const stubTimeValueGlobals = stubTimesValues[time].global;
            Object.keys(stubTimeValueGlobals).forEach((globalName) => {
              if (!selfDefinedGlobals.has(globalName)) {
                return;
              }
              if (!stubTimesValues[time].malloc) {
                stubTimesValues[time].malloc = {};
              }
              stubTimesValues[time].malloc[globalName] = {
                ...stubTimeValueGlobals[globalName],
                ...selfDefinedGlobals.get(globalName),
              };
              delete stubTimeValueGlobals[globalName];
            });
            if (Object.keys(stubTimesValues[time].global).length === 0) {
              delete stubTimesValues[time].global;
            }
          });
        });
      }
    }
  }
  // 处理桩函数输出检查项中的pointerTargets
  if (functionVariable.output && functionVariable.output.stubs) {
    Object.keys(functionVariable.output.stubs).forEach((stubFunctionName) => {
      if (!functionVariable.stubs
        || !functionVariable.stubs[stubFunctionName]
        || !functionVariable.stubs[stubFunctionName].pointerTargets
        || !testCase.data
        || !testCase.data.output
        || !testCase.data.output.stubs
        || !testCase.data.output.stubs[stubFunctionName]
        || !testCase.data.output.stubs[stubFunctionName]['@value']) {
        return;
      }
      const outputStubValue = testCase.data.output.stubs[stubFunctionName]['@value'];
      const funcVariableStubPointerTargets = functionVariable.stubs[stubFunctionName].pointerTargets;
      const stubPointerTargetTypes = ['@type', '@elementType', '@length'];
      Object.keys(outputStubValue).forEach((time) => {
        const usedPointerTargets = [];
        const outputStubTimeValue = outputStubValue[time];
        ['params'].forEach((key) => {
          if (!outputStubTimeValue[key]) {
            return;
          }
          const fieldValues = getTestCaseValues(outputStubTimeValue[key], '@value');
          Object.keys(fieldValues).forEach((fieldKey) => {
            const matchValues = fieldValues[fieldKey].match(/^@(.*)/);
            if (matchValues && matchValues.length === 2) {
              usedPointerTargets.push(matchValues[1]);
            }
          });
        });
        if (!outputStubTimeValue.pointerTargets) {
          return;
        }
        Object.keys(functionVariable.stubs[stubFunctionName].pointerTargets).forEach((pointerTargetName) => {
          if (!outputStubTimeValue.pointerTargets[pointerTargetName]) {
            return;
          }
          if (!usedPointerTargets.includes(pointerTargetName)) {
            delete outputStubTimeValue.pointerTargets[pointerTargetName];
            return;
          }
          stubPointerTargetTypes.forEach((typeName) => {
            if (funcVariableStubPointerTargets[pointerTargetName][typeName] && !outputStubTimeValue.pointerTargets[pointerTargetName][typeName]) {
              outputStubTimeValue.pointerTargets[pointerTargetName][typeName] = funcVariableStubPointerTargets[pointerTargetName][typeName];
            }
          });
        });
        Object.keys(outputStubTimeValue.pointerTargets).forEach((pointerTargetName) => {
          stubPointerTargetTypes.forEach((typeName) => {
            if (outputStubTimeValue.pointerTargets[pointerTargetName][typeName] === null) {
              delete outputStubTimeValue.pointerTargets[pointerTargetName][typeName];
            }
          });
        });
        if (utility.isEmpty(outputStubTimeValue.pointerTargets)) {
          delete outputStubTimeValue.pointerTargets;
        }
      });
    });
  }
}

/**
 * 移除指针目标
 * @param {String} functionId 函数id
 * @param {String} variableName 变量名
 * @return {Promise}
 */
function removeMallocVariable(functionId, variableName) {
  return new Promise((resolve, reject) => {
    getTestsOfFunctions(mongoose.Types.ObjectId(functionId))
      .then((tests) => {
        const modifiedTestIds = [];
        const promises = tests.filter((test) => test.data).map((test) => {
          const { malloc } = test.data;
          if (malloc) {
            delete malloc[variableName];
          }
          searchAndDelete(test.data, variableName, matchMallocName);
          modifiedTestIds.push(test._id);
          const doc = { $set: { kind: testKind.modified } };
          doc.$set.data = test.data;
          return TestCase.updateOne({ _id: test._id }, doc);
        });
        promises.push(codeDefects.setCodeDefectToManual({ functionId, testIds: modifiedTestIds }));
        return Promise.all(promises);
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 更新指针目标
 * @param {String} functionId 函数Id
 * @param {String} variableName 指针目标名称
 * @param {Object} info 修改信息
 * @returns {Promise}
 */
function updateMallocVariable(functionId, variableName, info) {
  return new Promise((resolve, reject) => {
    getTestsOfFunctions(functionId)
      .then((tests) => {
        const modifiedTestIds = [];
        const promises = tests.filter((test) => test.data).map((test) => {
          const { malloc } = test.data;
          if (malloc && malloc[variableName] && malloc[variableName]['@value'] && info['@length']) {
            const newLength = parseInt(info['@length'], 10); // undefined
            Object.keys(malloc[variableName]['@value']).forEach((index) => {
              if (parseInt(index, 10) >= newLength) {
                delete malloc[variableName]['@value'][index];
              }
            });
          }
          function update(obj, key) {
            const oldName = obj[key];
            if (oldName.endsWith(']')) {
              const index = parseInt(oldName.split('[')[1], 10);
              if (index >= info['@length']) {
                delete obj[key];
              }
            }
          }
          searchAndModify(test.data, variableName, matchMallocName, update);
          modifiedTestIds.push(test._id);
          const doc = { $set: { kind: testKind.modified } };
          doc.$set.data = test.data;
          return TestCase.updateOne({ _id: test._id }, doc);
        });
        promises.push(codeDefects.setCodeDefectToManual({ functionId, testIds: modifiedTestIds }));
        return Promise.all(promises);
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 移除指针目标值，保留引用关系
 * @param {String} functionId 函数Id
 * @param {String} variableName 指针目标名称
 * @returns {Promise}
 */
function removeMallocVariableValue(functionId, variableName) {
  functionId = mongoose.Types.ObjectId(functionId);
  const doc = { $unset: {}, $set: { kind: testKind.modified } };
  doc.$unset[`data.malloc.${variableName}`] = 1;
  doc.$unset[`data.output.malloc.${variableName}`] = 1;
  return Promise.all([
    TestCase.updateMany({ functionId }, doc),
    codeDefects.setCodeDefectToManual({ functionId }),
  ]);
}

/**
 * 重命名指针目标
 * @param {String} functionId 函数Id
 * @param {String} variableName 指针目标原名
 * @param {String} newVariableName 指针目标新名
 * @returns {Promise}
 */
function renameMallocVariable(functionId, variableName, newVariableName) {
  functionId = mongoose.Types.ObjectId(functionId);
  newVariableName = newVariableName || variableName;
  if (newVariableName === variableName) {
    return Promise.resolve();
  }
  return new Promise((resolve, reject) => {
    getTestsOfFunctions(functionId)
      .then((tests) => {
        const modifiedTestIds = [];
        const promises = tests.filter((test) => test.data).map((test) => {
          const { malloc, output } = test.data;
          function rename(obj, key) {
            obj[key] = obj[key].replace(variableName, newVariableName);
          }
          if (malloc) {
            malloc[newVariableName] = malloc[variableName];
            delete malloc[variableName];
          }
          if (output && output.malloc && output.malloc[variableName]) {
            output.malloc[newVariableName] = output.malloc[variableName];
            delete output.malloc[variableName];
          }
          searchAndModify(test.data, variableName, matchMallocName, rename);
          modifiedTestIds.push(test._id);
          const doc = { $set: { kind: testKind.modified } };
          doc.$set.data = test.data;
          return TestCase.updateOne({ _id: test._id }, doc);
        });
        promises.push(codeDefects.setCodeDefectToManual({ functionId, testIds: modifiedTestIds }));
        return Promise.all(promises);
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 移除桩函数
 * @param {String} functionId 函数id
 * @param {String} stubName 变量名
 * @return {Promise}
 */
function removeStub(functionId, stubName) {
  return new Promise((resolve, reject) => {
    getTestsOfFunctions(mongoose.Types.ObjectId(functionId))
      .then((tests) => {
        const modifiedTestIds = [];
        const promises = tests.filter((test) => test.data).map((test) => {
          const { stubs } = test.data;
          if (stubs) {
            delete stubs[stubName];
          }
          if (test.data && test.data.output && test.data.output.stubs && test.data.output.stubs[stubName]) {
            delete test.data.output.stubs[stubName];
          }
          searchAndDelete(test.data, stubName, (str, target) => str === target);
          modifiedTestIds.push(test._id);
          const doc = { $set: { kind: testKind.modified } };
          doc.$set.data = test.data;
          return TestCase.updateOne({ _id: test._id }, doc);
        });
        promises.push(codeDefects.setCodeDefectToManual({ functionId, testIds: modifiedTestIds }));
        return Promise.all(promises);
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 删除桩函数，保留引用关系
 * @param {String} functionId 函数Id
 * @param {String} stubName 桩函数名
 * @returns {Promise}
 */
function removeStubValue(functionId, stubName) {
  return new Promise((resolve, reject) => {
    getTestsOfFunctions(mongoose.Types.ObjectId(functionId))
      .then((tests) => {
        const modifiedTestIds = [];
        const promises = tests.filter((test) => test.data).map((test) => {
          modifiedTestIds.push(test._id);
          const doc = { $set: { kind: testKind.modified }, $unset: {} };
          doc.$unset[`data.stubs.${stubName}`] = 1;
          return TestCase.updateOne({ _id: test._id }, doc);
        });
        promises.push(codeDefects.setCodeDefectToManual({ functionId, testIds: modifiedTestIds }));
        return Promise.all(promises);
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 重命名桩函数
 * @param {String} functionId 函数Id
 * @param {String} stubName 原桩函数名
 * @param {String} newStubName 新桩函数名
 * @returns {Promise}
 */
function renameStub(functionId, stubName, newStubName) {
  newStubName = newStubName || stubName;
  if (stubName === newStubName) {
    return Promise.resolve();
  }
  return new Promise((resolve, reject) => {
    getTestsOfFunctions(functionId)
      .then((tests) => {
        const modifiedTestIds = [];
        const promises = tests.filter((test) => test.data).map((test) => {
          function match(str, target) {
            return str === target;
          }
          function modify(obj, key) {
            obj[key] = obj[key].replace(stubName, newStubName);
          }
          const { stubs } = test.data;
          if (stubs && stubs[stubName]) {
            stubs[newStubName] = stubs[stubName];
            delete stubs[stubName];
          }
          if (test.data.output && test.data.output.stubs && test.data.output.stubs[stubName]) {
            test.data.output.stubs[newStubName] = test.data.output.stubs[stubName];
            delete test.data.output.stubs[stubName];
          }
          searchAndModify(test.data, stubName, match, modify);
          modifiedTestIds.push(test._id);
          const doc = { $set: { kind: testKind.modified } };
          doc.$set.data = test.data;
          return TestCase.updateOne({ _id: test._id }, doc);
        });
        promises.push(codeDefects.setCodeDefectToManual({ functionId, testIds: modifiedTestIds }));
        return Promise.all(promises);
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 更新桩函数
 * @param {String} functionId 函数Id
 * @param {String} stubName 桩函数名
 * @param {Number} times 调用次数
 * @returns {Promise}
 */
function updateStub(functionId, stubName, times) {
  return new Promise((resolve, reject) => {
    getTestsOfFunctions(functionId)
      .then((tests) => {
        const modifiedTestIds = [];
        const promises = tests.filter((test) => test.data).map((test) => {
          const { stubs } = test.data;
          if (stubs && stubs[stubName] && times) {
            Object.keys(stubs[stubName]['@value']).forEach((time) => {
              if (parseInt(time, 10) > times) {
                delete stubs[stubName]['@value'][time];
              }
            });
          }
          modifiedTestIds.push(test._id);
          const doc = { $set: { kind: testKind.modified } };
          doc.$set.data = test.data;
          return TestCase.updateOne({ _id: test._id }, doc);
        });
        promises.push(codeDefects.setCodeDefectToManual({ functionId, testIds: modifiedTestIds }));
        return Promise.all(promises);
      })
      .then(() => {
        resolve();
      })
      .catch(reject);
  });
}
/**
 * 移除测试用例中桩函数下的指针目标
 * @param {Object} stub 桩函数
 * @param {String} pointerTargetName 桩函数下指针目标名
 * return stub
 */
function removeStubPointerTargetOfTestCase(stub, pointerTargetName) {
  if (!stub['@value']) {
    return stub;
  }
  const stubValues = stub['@value'];
  Object.keys(stubValues).forEach((stubTime) => {
    if (!stubValues[stubTime].pointerTargets) {
      return;
    }
    if (stubValues[stubTime].pointerTargets[pointerTargetName]) {
      delete stubValues[stubTime].pointerTargets[pointerTargetName];
    }
    if (Object.keys(stubValues[stubTime].pointerTargets).length === 0) {
      delete stubValues[stubTime].pointerTargets;
    }
  });
  return stub;
}
/**
 * 移除桩函数指针目标
 * @param {String} functionId 函数id
 * @param {String} stubName 桩函数名
 * @param {String} pointerTargetName 指针目标名
 * @param {String} stubKey 桩函数键值
 * @return {Promise}
 */
function removeStubPointerTarget(functionId, stubName, pointerTargetName, stubKey = 'stubs') {
  return new Promise((resolve, reject) => {
    getTestsOfFunctions(functionId)
      .then((tests) => {
        const promises = [];
        const modifiedTestIds = [];
        tests.filter((test) => test.data).forEach((test) => {
          const stubs = test.data[stubKey];
          const outputStubs = test.data.output && test.data.output[stubKey] ? test.data.output[stubKey] : {};
          const doc = { $set: {} };
          [
            {
              key: 'data',
              stub: stubs,
            },
            {
              key: 'data.output',
              stub: outputStubs,
            },
          ].forEach(({ key, stub }) => {
            if (stub && stub[stubName]) {
              searchAndDelete(stub[stubName], pointerTargetName, matchStubPointerTargetName);
              doc.$set[`${key}.${stubKey}.${stubName}`] = removeStubPointerTargetOfTestCase(stub[stubName], pointerTargetName);
              doc.$set.kind = testKind.modified;
              modifiedTestIds.push(test._id);
            }
          });
          promises.push(TestCase.updateOne({ _id: test._id }, doc));
        });
        promises.push(codeDefects.setCodeDefectToManual({ functionId, testIds: modifiedTestIds }));
        return Promise.all(promises);
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 移除桩函数指针目标，保留引用关系
 * @param {String} functionId
 * @param {String} stubName 桩函数名
 * @param {String} pointerTargetName 需移除桩函数指针目标
 * @param {String} stubKey 桩函数键值
 * @returns {Promise}
 */
function removeStubPointerTargetValue(functionId, stubName, pointerTargetName, stubKey = 'stubs') {
  return new Promise((resolve, reject) => {
    getTestsOfFunctions(mongoose.Types.ObjectId(functionId))
      .then((tests) => {
        const promises = [];
        const modifiedTestIds = [];
        tests.filter((test) => test.data).forEach((test) => {
          const doc = { $set: {} };
          const stubs = test.data[stubKey];
          const outputStubs = test.data.output && test.data.output[stubKey] ? test.data.output[stubKey] : {};
          [
            {
              key: 'data',
              stub: stubs,
            },
            {
              key: 'data.output',
              stub: outputStubs,
            },
          ].forEach(({ key, stub }) => {
            if (!stub || !stub[stubName] || !stub[stubName]['@value']) {
              return;
            }
            const value = stub[stubName]['@value'];
            Object.keys(value).forEach((time) => {
              if (value[time].pointerTargets && value[time].pointerTargets[pointerTargetName]) {
                delete value[time].pointerTargets[pointerTargetName]['@value'];
              }
            });
            doc.$set[`${key}.${stubKey}.${stubName}.@value`] = stub[stubName]['@value'];
            doc.$set.kind = testKind.modified;
            modifiedTestIds.push(test._id);
          });
          promises.push(TestCase.updateOne({ _id: test._id }, doc));
        });
        promises.push(codeDefects.setCodeDefectToManual({ functionId, testIds: modifiedTestIds }));
        return Promise.all(promises);
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 更新桩函数指针目标
 * @param {String} functionId 函数Id
 * @param {String} stubName 桩函数名
 * @param {String} pointerTargetName 指针目标名称
 * @param {Number} newLength 新数组长度
 * @param {String} stubKey 桩函数键值
 * @returns {Promise}
 */
function updateStubPointerTarget(functionId, stubName, pointerTargetName, newLength, stubKey = 'stubs') {
  return new Promise((resolve, reject) => {
    getTestsOfFunctions(mongoose.Types.ObjectId(functionId))
      .then((tests) => {
        const promises = [];
        const modifiedTestIds = [];
        tests.filter((test) => test.data).forEach((test) => {
          const stubs = test.data[stubKey];
          const outputStubs = test.data.output && test.data.output[stubKey] ? test.data.output[stubKey] : {};
          const doc = { $set: {} };
          [
            {
              key: 'data',
              stub: stubs,
            },
            {
              key: 'data.output',
              stub: outputStubs,
            },
          ].forEach(({ key, stub }) => {
            if (stub && stub[stubName]) {
              const stubValue = stub[stubName]['@value'];
              if (stubValue) {
                Object.keys(stubValue).filter((time) => stubValue[time].pointerTargets)
                  .forEach((time) => {
                    const pointerTarget = stubValue[time].pointerTargets[pointerTargetName];
                    if (pointerTarget) {
                      Object.keys(pointerTarget['@value']).forEach((index) => {
                        if (parseInt(index, 10) >= newLength) {
                          delete pointerTarget['@value'][index];
                        }
                      });
                    }
                  });
              }
              doc.$set[`${key}.${stubKey}.${stubName}`] = stub[stubName];
              doc.$set.kind = testKind.modified;
              modifiedTestIds.push(test._id);
            }
          });
          // eslint-disable-next-line no-underscore-dangle
          promises.push(TestCase.updateOne({ _id: test._id }, doc));
        });
        promises.push(codeDefects.setCodeDefectToManual({ functionId, testIds: modifiedTestIds }));
        return Promise.all(promises);
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 重命名桩函数指针目标
 * @param {String} functionId 函数Id
 * @param {String} stubName 桩函数名
 * @param {String} pointerTargetName 桩函数指针目标原名
 * @param {String} newName 桩函数指针目标新名
 * @param {String} stubKey 桩函数键值
 * @returns {Promise}
 */
function renameStubPointerTarget(functionId, stubName, pointerTargetName, newName, stubKey = 'stubs') {
  newName = newName || pointerTargetName;
  if (newName === pointerTargetName) {
    return Promise.resolve();
  }
  return new Promise((resolve, reject) => {
    getTestsOfFunctions(mongoose.Types.ObjectId(functionId))
      .then((tests) => {
        const promises = [];
        const modifiedTestIds = [];
        tests.filter((test) => test.data).forEach((test) => {
          const stubs = test.data[stubKey];
          const outputStubs = test.data.output && test.data.output[stubKey] ? test.data.output[stubKey] : {};
          function rename(obj, key) {
            obj[key] = obj[key].replace(pointerTargetName, newName);
          }
          const doc = { $set: {} };
          [
            {
              key: 'data',
              stub: stubs,
            },
            {
              key: 'data.output',
              stub: outputStubs,
            },
          ].forEach(({ key, stub }) => {
            if (stub && stub[stubName]) {
              const stubValue = stub[stubName]['@value'];
              if (stubValue) {
                Object.keys(stubValue).filter((time) => stubValue[time].pointerTargets)
                  .forEach((time) => {
                    const pointerTarget = stubValue[time].pointerTargets[pointerTargetName];
                    if (pointerTarget) {
                      if (newName !== pointerTargetName) {
                        stubValue[time].pointerTargets[newName] = pointerTarget;
                        delete stubValue[time].pointerTargets[pointerTargetName];
                      }
                    }
                  });
              }
              searchAndModify(stub[stubName], pointerTargetName, matchStubPointerTargetName, rename);
              doc.$set[`${key}.${stubKey}.${stubName}`] = stub[stubName];
              doc.$set.kind = testKind.modified;
              modifiedTestIds.push(test._id);
            }
          });
          promises.push(TestCase.updateOne({ _id: test._id }, doc));
        });
        promises.push(codeDefects.setCodeDefectToManual({ functionId, testIds: modifiedTestIds }));
        return Promise.all(promises);
      })
      .then(() => {
        resolve();
      })
      .catch(reject);
  });
}

/**
 * 项目总览- 测试用例列表
 * @param {String} versionId 版本id
 * @param {Object} param1 options参数
 * @returns {Promise}
 */
async function getOverview(versionId, {
  fileId,
  functionId,
  resultKind,
  kind,
  page = 1,
  pageSize = 10,
  sortBy = '_id',
  order = 'asc',
  caseChange,
  tested = undefined, // 是否已测试
} = {}) {
  const version = await ProjectVersion.findById(mongoose.Types.ObjectId(versionId), [
    'oldVersionId',
  ], { lean: true });
  resultKind = resultKind ? [].concat(resultKind).map((s) => parseInt(s, 10)) : undefined;
  kind = kind ? [].concat(kind) : undefined;
  const _and = [
    {
      versionId: mongoose.Types.ObjectId(versionId),
    },
  ];
  if (typeof tested === 'boolean') {
    if (tested) {
      _and.push({
        resultKind: { $ne: testResultKind.unknown },
      });
    } else {
      _and.push({
        resultKind: testResultKind.unknown,
      });
    }
  }
  if (resultKind) {
    _and.push({
      resultKind: { $in: resultKind },
    });
  }
  if (kind && kind.length === 1) {
    if (kind[0] === '0') {
      _and.push({
        kind: 0,
      });
    } else {
      _and.push({
        kind: { $ne: 0 },
      });
    }
  }
  if (fileId) {
    _and.push({
      fileId: mongoose.Types.ObjectId(fileId),
    });
  }
  if (functionId) {
    _and.push({
      functionId: mongoose.Types.ObjectId(functionId),
    });
  }
  if (version.oldVersionId && caseChange !== undefined) {
    caseChange = parseInt(caseChange, 10);
    if (caseChange) {
      // 用例新增
      _and.push({
        sourceInRegression: caseChange,
      });
    } else {
      // 用例复用，兼容旧版本数据
      _and.push({
        $or: [
          { sourceInRegression: caseChange },
          { sourceInRegression: { $exists: false } },
        ],
      });
    }
  }
  const ret = await TestCase.aggregate([
    {
      $match: {
        $and: _and,
      },
    },
    {
      $lookup: {
        from: 'functions',
        localField: 'functionId',
        foreignField: '_id',
        as: 'function',
      },
    },
    {
      $unwind: '$function',
    },
    {
      $match: {
        'function.collectCoverage': true,
      },
    },
    {
      $count: 'total',
    },
  ]);
  const [{ total }] = ret && ret.length ? ret : [{ total: 0 }];
  const dbProject = {
    description: 1,
    functionId: 1,
    fileId: 1,
    function: {
      _id: 1,
      functionName: 1,
    },
    file: {
      _id: 1,
      path: 1,
    },
    resultKind: 1,
    kind: 1,
    number: 1,
    identifierNumber: 1,
  };
  if (version.oldVersionId) {
    dbProject.sourceInRegression = 1;
  }
  const arr = await TestCase.aggregate([
    {
      $match: {
        $and: _and,
      },
    },
    {
      $lookup: {
        from: 'functions',
        localField: 'functionId',
        foreignField: '_id',
        as: 'function',
      },
    },
    {
      $unwind: '$function',
    },
    {
      $match: {
        'function.collectCoverage': true,
      },
    },
    {
      $lookup: {
        from: 'files',
        localField: 'fileId',
        foreignField: '_id',
        as: 'file',
      },
    },
    {
      $unwind: '$file',
    },
    {
      $sort: {
        // fileId: 1,
        // functionId: 1,
        // number: 1,
        [sortBy]: order.toLowerCase() === 'asc' ? 1 : -1,
      },
    },
    {
      $skip: (page - 1) * pageSize,
    },
    {
      $limit: pageSize,
    },
    {
      $project: dbProject,
    },

  ]).allowDiskUse(true);
  if (version.oldVersionId) {
    // 兼容旧版本数据
    arr.forEach((data) => {
      data.caseChange = data.sourceInRegression === undefined ? 0 : data.sourceInRegression;
      delete data.sourceInRegression;
    });
  }
  return [arr, total];
}

/**
 * 项目总览-测试用例列表下载xlsx
 * @param {String} version 版本
 * @param {String} excelName 文件名
 * @param {Object} locale 国际化
 * @param {boolean} tested 是否已测试
 */
async function generateOverviewXlsx(version, excelName, locale, tested) {
  const page = 1;
  const pageSize = 1048575;
  const versionInfo = await ProjectVersion.findById(mongoose.Types.ObjectId(version.versionId), [
    'oldVersionId',
  ], { lean: true });
  const [testList, testTotal] = await getOverview(version.versionId, { page, pageSize, tested });

  // excel cache path
  const outputDir = path.join(cacheUtil.getConfigPath('exportDir'), randomstring.generate(16));
  await fs.ensureDir(outputDir);
  const outPath = path.join(outputDir, excelName);
  const fileExcel = new Excel();
  fileExcel.addSheet('Sheet1');

  // build data
  const columns = [{
    header: locale.__('overview.test.testName'),
    key: 'testName',
    width: 16.93,
  },
  {
    header: locale.__('overview.test.testIdentifier'),
    key: 'testIdentifier',
    width: 16.93,
  },
  {
    header: locale.__('overview.test.functionName'),
    key: 'functionName',
    width: 16.93,
  }, {
    header: locale.__('overview.test.filePath'),
    key: 'filePath',
    width: 16.93,
  }];
  if (versionInfo.oldVersionId) {
    columns.push({
      header: locale.__('overview.test.sourceInRegression'),
      key: 'sourceInRegression',
      width: 16.93,
    });
  }
  columns.push(...[{
    header: locale.__('overview.test.resultKind'),
    key: 'resultKind',
    width: 16.93,
  }, {
    header: locale.__('overview.test.testKind'),
    key: 'testKind',
    width: 16.93,
  }]);

  const data = [];
  testList.forEach((test) => {
    const testData = {
      testName: locale.__('overview.test.namePrefix').concat(String(test.number)),
      testIdentifier: getTestIdentifier({
        testIdentifierRule: version.testIdentifierRule,
        functionName: test.function.functionName,
        filepath: test.file.path,
        identifierNumber: test.identifierNumber,
      }),
      functionName: test.function.functionName,
      filePath: test.file.path,
      resultKind: locale.__('overview.test.testResult.'.concat(utility.getObjectKeyByValue(testResultKind, test.resultKind))),
      testKind: test.kind ? locale.__('overview.test.autoGenerated') : locale.__('overview.test.createdManually'),
      sourceInRegression: versionInfo.oldVersionId ? locale.__('overview.test.'.concat(test.caseChange ? 'addedCase' : 'reusedCase')) : undefined,
    };
    data.push(testData);
  });

  fileExcel.addRows(columns, data);

  // style
  ['A', 'B', 'C', 'D', 'E', 'F', 'G'].forEach((column) => {
    for (let i = 1; i <= testTotal + 1; i += 1) {
      fileExcel.setCell(column.concat(String(i)));
    }
  });

  await fileExcel.writeFile(outPath);
  return [outPath, outputDir];
}

async function testCountOverview(versionId, {
  fileId,
  functionId,
} = {}) {
  const _and = [
    {
      versionId: mongoose.Types.ObjectId(versionId),
    },
  ];

  if (fileId) {
    _and.push({
      fileId: mongoose.Types.ObjectId(fileId),
    });
  }
  if (functionId) {
    _and.push({
      functionId: mongoose.Types.ObjectId(functionId),
    });
  }

  const ret = await TestCase.aggregate([
    {
      $match: {
        $and: _and,
      },
    },
    {
      $count: 'total',
    },
  ]);
  const [{ total }] = ret && ret.length ? ret : [{ total: 0 }];

  return total;
}

/**
 * 获取绝对地址表达式默认值
 * @param expr 表达式
 * @param variables 函数变量信息
 * @returns {null|*}
 */
function getDefaultFixedAddressValue(expr, variables) {
  if (!variables.fixedAddrs || !variables.fixedAddrs.exprs[expr]) {
    return null;
  }
  const value = variables.fixedAddrs.exprs[expr]['@value'];
  if (!variables.fixedAddrs.bases || !variables.fixedAddrs.bases[value]) {
    return null;
  }
  return variables.fixedAddrs.bases[value]['@hex'];
}

/**
 * 为测试用例设置绝对地址表达式默认值
 * @param functionId 函数id
 * @param exprs 表达式
 * @param variables 函数变量信息
 * @returns {Promise}
 */
function addFixedAddressDefaultValue(functionId, exprs, variables) {
  if (!exprs || exprs.length === 0) {
    return Promise.resolve();
  }
  const doc = { $set: {} };
  exprs.forEach((expr) => {
    const defaultValue = getDefaultFixedAddressValue(expr, variables);
    if (!defaultValue) {
      return;
    }
    doc.$set[`data.fixedAddrs.exprs.${expr}`] = { '@value': defaultValue };
  });
  return TestCase.updateMany({ functionId: mongoose.Types.ObjectId(functionId) }, doc);
}

/**
 * 获得测试用例结果分组数据
 * @param Array|String groupFields 分组字段
 * @param Array|String resultKinds 测试结果类型
 * @param Array|String fileIds 文件Ids
 * @param Array|String functionIds 函数Ids
 * @returns {Promise<{}>}
 */
function getTestResultsGroup(
  groupFields,
  {
    resultKinds = [],
    fileIds = [],
    functionIds = [],
    versionId = null,
  }
) {
  if (groupFields && !utility.isArrayType(groupFields)) {
    groupFields = [groupFields];
  }
  if (resultKinds && !utility.isArrayType(resultKinds)) {
    resultKinds = [resultKinds];
  }
  if (!utility.isArrayType(fileIds)) {
    fileIds = [fileIds];
  }
  if (!utility.isArrayType(functionIds)) {
    functionIds = [functionIds];
  }
  if (groupFields.length === 0) {
    return Promise.resolve({});
  }
  const options = {};
  if (resultKinds.length > 0) {
    options.resultKind = { $in: resultKinds.map((value) => Number(value)) };
  }
  if (fileIds.length > 0) {
    options.fileId = { $in: fileIds.map((id) => mongoose.Types.ObjectId(id)) };
  }
  if (functionIds.length > 0) {
    options.functionId = { $in: functionIds.map((id) => mongoose.Types.ObjectId(id)) };
  }
  if (versionId) {
    options.versionId = mongoose.Types.ObjectId(versionId);
  }
  const group = {};
  group._id = groupFields.reduce((pre, field) => {
    pre[field] = `$${field}`;
    return pre;
  }, {});
  return TestCase.aggregate([
    { $match: options },
    { $group: group },
  ])
    .then((results) => {
      const groupInfo = {};
      results.forEach((result) => {
        groupInfo[Object.values(result._id).join('|')] = 1;
      });
      return groupInfo;
    });
}
/**
 * 测试用例获取可选文件
 * @param {string} functionId 函数id
 * @param {string} key 参数名
 * @param {string} type  参数类型
 * @param {object} $projectVersion  版本信息
 */
async function getTestCaseFiles(functionId, key, type) {
  let files = [];
  const funcInfo = await Func.findById(functionId);
  type = Number.parseInt(type, 10);
  // 处理全局变量
  if (type === testParameterKind.globalVariable) {
    const resFiles = await FileGlobalVariable.aggregate([
      {
        $match: {
          versionId: mongoose.Types.ObjectId(funcInfo.versionId),
          [`globals.${key}`]: { $exists: true },
        },
      },
      {
        $lookup: {
          from: 'files',
          localField: 'fileId',
          foreignField: '_id',
          as: 'newFiles',
        },
      },
      {
        $unwind: '$newFiles',
      },
      {
        $project: {
          newFiles: {
            fileId: '$newFiles._id',
            path: 1,
            fileName: 1,

          },
          attributes: `$globals.${key}.@attributes`,
        },
      },
    ]);
    resFiles.forEach((item) => {
      if (!item.attributes.includes('isStatic')) {
        files.push(item.newFiles);
      }
    });
  }
  // 绝对地址基地址
  if (type === testParameterKind.fixedAddrsBase) {
    files = await FunctionVariable.aggregate([
      {
        $match: {
          versionId: mongoose.Types.ObjectId(funcInfo.versionId),
          [`fixedAddrs.bases.${key}`]: { $exists: true },
        },
      },
      {
        $lookup: {
          from: 'files',
          localField: 'fileId',
          foreignField: '_id',
          as: 'newFiles',
        },
      },
      {
        $unwind: '$newFiles',
      },
      {
        $project: {
          _id: 0,
          fileId: '$newFiles._id',
          path: '$newFiles.path',
          fileName: '$newFiles.fileName',
        },
      },
      {
        $group: {
          _id: {
            fileId: '$fileId',
            path: '$path',
            fileName: '$fileName',
          },
        },
      },
      {
        $project: {
          _id: 0,
          fileId: '$_id.fileId',
          path: '$_id.path',
          fileName: '$_id.fileName',
        },
      },
    ]);
  }
  return files;
}

// eslint-disable-next-line no-unused-vars
async function getFileIdsByTestCaseFilePath(testData, versionId, { funcVariables = undefined }) {
  const filePaths = new Set();
  [testData, funcVariables].forEach((data) => {
    Object.keys(data).forEach((key) => {
      if (key === 'variables') {
        if (data.variables && data.variables.global) {
          Object.keys(data.variables.global).forEach((g) => {
            if (data.variables.global[g].filePath) {
              filePaths.add(data.variables.global[g].filePath);
            }
          });
        }
        if (data.variables && data.variables.statics) {
          Object.keys(data.variables.statics).forEach((s) => {
            if (data.variables.statics[s].filePath) {
              filePaths.add(data.variables.statics[s].filePath);
            }
          });
        }
      } else if (key === 'fixedAddrs') {
        if (data.fixedAddrs && data.fixedAddrs.exprs) {
          Object.keys(data.fixedAddrs.exprs).forEach((e) => {
            if (data.fixedAddrs.exprs[e].filePath) {
              filePaths.add(data.fixedAddrs.exprs[e].filePath);
            }
          });
        }
      } else {
        Object.keys(data[key]).forEach((item) => {
          if (data[key][item].filePath) {
            filePaths.add(data[key][item].filePath);
          }
        });
      }
    });
  });

  const resFilePaths = await File.find({
    versionId: mongoose.Types.ObjectId(versionId),
    path: { $in: [...filePaths] },
  });
  return resFilePaths.map((file) => file._id);
}
/**
 * 通过testId获取测试用例涉及的所有文件
 * @param {string} testId 测试用例id
 * @param {Object} testcase 测试用例
 * @param {Object} funcVariables 函数参数
 */
async function getFilesByTestId(testId, testcase = null, funcVariables = null) {
  const fileIds = new Set();
  if (!testcase) {
    testcase = await TestCase.findById(testId);
  }
  const {
    fileId,
    functionId,
  } = testcase;
  if (!funcVariables) {
    funcVariables = await functions.getRelationFuncsVariables(functionId);
  }
  if (!funcVariables.mangledId) {
    return [];
  }
  funcVariables.mangledId.forEach((id) => {
    const [fileId] = id.split('_');
    fileIds.add(fileId);
  });
  fileIds.add(fileId.toString());
  [funcVariables, testcase.data || {}].forEach((funcData) => {
    Object.keys(funcData).forEach((key) => {
      if (!utility.isObjectType(funcData[key])) {
        return;
      }
      if (key === 'variables') {
        if (funcData.variables && funcData.variables.global) {
          Object.keys(funcData.variables.global).forEach((g) => {
            if (funcData.variables.global[g].fileId) {
              fileIds.add(funcData.variables.global[g].fileId.toString());
            }
          });
        }
        if (funcData.variables && funcData.variables.statics) {
          Object.keys(funcData.variables.statics).forEach((s) => {
            if (funcData.variables.statics[s].fileId) {
              fileIds.add(funcData.variables.statics[s].fileId.toString());
            }
          });
        }
      } else if (key === 'fixedAddrs') {
        if (funcData.fixedAddrs && funcData.fixedAddrs.bases) {
          Object.keys(funcData.fixedAddrs.bases).forEach((b) => {
            if (funcData.fixedAddrs.bases[b].fileId) {
              fileIds.add(funcData.fixedAddrs.bases[b].fileId.toString());
            }
          });
        }
        if (funcData.fixedAddrs && funcData.fixedAddrs.exprs) {
          Object.keys(funcData.fixedAddrs.exprs).forEach((e) => {
            if (funcData.fixedAddrs.exprs[e].fileId) {
              fileIds.add(funcData.fixedAddrs.exprs[e].fileId.toString());
            }
          });
        }
      } else {
        Object.keys(funcData[key]).forEach((item) => {
          if (funcData[key][item] && funcData[key][item].fileId) {
            fileIds.add(funcData[key][item].fileId.toString());
          }
        });
      }
    });
  });
  return [...fileIds];
}
/**
 * 获取集成测试测试用例文件类型系统
 * @param {String} testCaseId  测试用例id
 * @param {Object} testCase 测试用例
 * @param {Map} allFileTypeMap 当前版本所有文件类型系统
 * @param {Map} allFuncMap 当前版本所有函数
 * @param {Object} funcVariables 被测函数varibale
 * @param {Map} allFileMap 版本下所有文件map
 * @param {Map} allFileGlobals 版本下所有文件下的全局变量
 * @param {Array} allFuncVariableStubs 当前版本所有函数variable下所有桩函数
 * @returns types 返回文件类型object
 */
async function integrationGetFilesTypeSystemByTestId({
  testCaseId = undefined,
  testCase = undefined,
  allRelationFuncsMap = undefined,
  allFileTypeMap = undefined,
  allFuncMap = undefined,
  funcVariables,
  allFuncVariableStubs,
  allFileMap,
  allFileGlobals,
}) {
  const types = {};
  if (!testCase) {
    testCase = await TestCase.findById(testCaseId);
  }
  const {
    versionId, functionId, data,
  } = testCase;
  if (!allFuncMap) {
    allFuncMap = new Map();
    const funcs = await Func.find({ versionId, kind: { $in: fileElement.getCountableKinds() } }, {
      _id: 1, fileId: 1, functionName: 1, functionAttrs: 1, sourceParsedFile: 1, kind: 1,
    }).lean();
    funcs.forEach((func) => {
      allFuncMap.set(func._id.toString(), func);
    });
  }
  let retrieveRelationFuncs;
  if (allRelationFuncsMap) {
    retrieveRelationFuncs = allRelationFuncsMap.get(functionId.toString());
  } else {
    retrieveRelationFuncs = await invokeRelation.retrieveRelations(functionId, { initRelations: true });
  }
  // 入口函数所属文件也有可能是未解析的文件,将入口函数填入调用关系第一位统一处理获取类型
  retrieveRelationFuncs.unshift({ toFuncId: functionId });
  const addedSourceParsedFileIds = new Set();
  const toFuncFileIds = [];
  retrieveRelationFuncs.forEach((invokeFunc) => {
    const invokeFunctionId = invokeFunc.toFuncId ? invokeFunc.toFuncId.toString() : invokeFunc.fromFuncId.toString();
    // 补充toFuncFileId type
    if (invokeFunc.toFuncFileId && !types[invokeFunc.toFuncFileId.toString()]) {
      toFuncFileIds.push(invokeFunc.toFuncFileId.toString());
    }
    const func = allFuncMap.get(invokeFunctionId);
    if (!func) {
      return;
    }
    const invokeuncFileId = func.fileId.toString();
    if (types[invokeuncFileId]) {
      return;
    }
    // 该函数所属文件已经解析
    if (!func.sourceParsedFile || func.sourceParsedFile.length === 0) {
      types[invokeuncFileId] = invokeuncFileId;
      return;
    }
    // 该函数所属文件未解析
    let sourceParsedFileId;
    func.sourceParsedFile.forEach((fileId) => {
      if (addedSourceParsedFileIds.has(fileId.toString())) {
        sourceParsedFileId = fileId.toString();
      }
    });
    // 来源文件含有被上一个函数使用过的文件就延续使用该文件类型
    if (sourceParsedFileId) {
      types[invokeuncFileId] = sourceParsedFileId;
    } else {
      // 源文件类型在已复制的函数中没有被使用过，就默认使用第一个来源文件
      sourceParsedFileId = func.sourceParsedFile[0].toString();
      types[invokeuncFileId] = sourceParsedFileId;
      addedSourceParsedFileIds.add(sourceParsedFileId);
    }
  });
  // 测试用例全局变量自定义选择了文件
  // note:全局变量定义的文件肯定是已经解析
  if (data && data.variables && data.variables.global) {
    Object.keys(data.variables.global).forEach((globalName) => {
      const globalFileId = data.variables.global[globalName].fileId;
      if (globalFileId && !types[globalFileId.toString()]) {
        types[globalFileId.toString()] = globalFileId.toString();
      }
    });
  }
  // 将函数variable原有全局变量选择的文件的类型也加进去
  if (!funcVariables) {
    funcVariables = await functions.getRelationFuncsVariables(functionId, {
      allFuncVariableStubs,
      allFileMap,
      allFileGlobals,
      allFuncMap,
    });
  }
  if (funcVariables.variables && funcVariables.variables.global) {
    const variableGlobals = funcVariables.variables.global;
    Object.keys(variableGlobals).forEach((globalName) => {
      const globalFileId = variableGlobals[globalName].fileId;
      if (globalFileId && !types[globalFileId.toString()]) {
        types[globalFileId.toString()] = globalFileId.toString();
      }
    });
  }
  if (funcVariables.fixedAddrs && funcVariables.fixedAddrs.bases) {
    const variableFixedAddrsBases = funcVariables.fixedAddrs.bases;
    Object.keys(variableFixedAddrsBases).forEach((baseName) => {
      const fixedAddrsBasesFileId = variableFixedAddrsBases[baseName].fileId;
      if (fixedAddrsBasesFileId && !types[fixedAddrsBasesFileId.toString()]) {
        types[fixedAddrsBasesFileId.toString()] = fixedAddrsBasesFileId.toString();
      }
    });
  }
  // 补全toFuncFileId type
  toFuncFileIds.forEach((id) => {
    if (!types[id]) {
      types[id] = id;
    }
  });
  if (allFileTypeMap) {
    Object.keys(types).forEach((key) => {
      types[key] = allFileTypeMap.get(types[key]);
    });
  } else {
    const filesTypes = await fileModel.getTypes(Object.values(types));
    Object.keys(types).forEach((key) => {
      const newTypes = filesTypes.filter((item) => item.fileId.toString() === types[key]);
      if (newTypes.length === 0) {
        delete types[key];
        return;
      }
      types[key] = newTypes[0].types;
    });
  }
  return { types, sourceParsedFiles: addedSourceParsedFileIds };
}

/**
 * 将测试用例中的全局变量，绝对地址表达式，桩函数，局部静态变量key中文件id/函数id换成路径
 * @param {object} testCase 测试用例数据
 * @param {object} allFuncsMap 当前版本所有函数map
 * @param {object} allFileIdsOfVersion 当前版本所有文件map key是fileid
 * @param {object} allFilePathOfVersion 当前版本所有文件map key是filePath
 * @param {String} entryFileId 入口函数文件id
 * @returns
 */
function replaceFileIdOrFuncIdToPath(testcase, allFuncsMap, allFileIdsOfVersion, entryFileId) {
  const data = testcase.data ? testcase.data : testcase;
  if (data.stubs) {
    data.stubs = handleIntegrationVariables.handleStubs(data.stubs, {
      type: 'idToPath',
      allFileIdsOfVersion,
    });
  }
  if (data.variables) {
    if (data.variables.global) {
      data.variables.global = handleIntegrationVariables.handleGlobals(data.variables.global, {
        type: 'idToPath',
        allFileIdsOfVersion,
        entryFileId,
      });
    }
    if (data.variables.statics) {
      data.variables.statics = handleIntegrationVariables.handleStatics(data.variables.statics, {
        type: 'idToPath',
        allFuncIdsMap: allFuncsMap,
        allFileIdsOfVersion,
      });
    }
  }
  if (data.fixedAddrs && data.fixedAddrs.exprs) {
    data.fixedAddrs.exprs = handleIntegrationVariables.handleFixedAddressExprs(data.fixedAddrs.exprs, {
      type: 'idToPath',
      allFuncIdsMap: allFuncsMap,
      allFileIdsOfVersion,
    });
  }
  if (data.output) {
    if (data.output.global) {
      data.output.global = handleIntegrationVariables.handleGlobals(data.output.global, {
        type: 'idToPath',
        allFileIdsOfVersion,
        entryFileId,
      });
    }
    if (data.output.statics) {
      data.output.statics = handleIntegrationVariables.handleStatics(data.output.statics, {
        type: 'idToPath',
        allFuncIdsMap: allFuncsMap,
        allFileIdsOfVersion,
      });
    }
  }

  return testcase;
}

/**
 * 将测试中的文件或者函数路径替换成文件id和函数id
 * @param {object} test 导入的测试用例数据
 * @param {object} allFilePathOfVersion 当前版本的所有文件key是文件path
 * @param {object} allFuncsOfFileAndMangledId 当前版本的所有函数keyshi fileid-mangledId
 * @param {object} originalVariables 导入的原始的variable
 * @param {object} newVariables 当前函数的variable
 */
async function replaceImportTestCasePath(
  test,
  allFilePathOfVersion,
  allFuncsOfFileAndMangledId,
  originalVariables,
  newVariables
) {
  // 单元测试导入集成测试
  if (test.versionType === projectVersions.versionType.unitTest) {
    if (test.data.stubs && originalVariables.stubs) {
      const newStubs = newVariables.stubs ? { ...newVariables.stubs } : {};
      Object.keys(test.data.stubs).forEach((stubName) => {
        // 函数解析带出来的桩函数，在新的variable中不存在时要去掉.
        if (originalVariables.stubs[stubName].stubsTriggerKind !== stubTriggerKind.manual && !newStubs[stubName]) {
          delete test.data.stubs[stubName];
        }
      });
    }
    return test;
  }
  // 处理集成测试测试用例数据
  if (test.data.variables) {
    if (test.data.variables.global) {
      test.data.variables.global = handleIntegrationVariables.handleGlobals(test.data.variables.global, {
        type: 'pathToId',
        allFilePathOfVersion,
      });
    }
    if (test.data.variables.statics) {
      test.data.variables.statics = handleIntegrationVariables.handleStatics(test.data.variables.statics, {
        type: 'pathToId',
        allFilePathOfVersion,
        allFuncsOfFileAndMangledId,
      });
    }
  }
  if (test.data.malloc) {
    test.data.malloc = handleIntegrationVariables.handleMallocs(test.data.malloc, {
      type: 'pathToid',
      allFilePathOfVersion,
    });
  }
  if (test.data.stubs) {
    test.data.stubs = handleIntegrationVariables.handleStubs(test.data.stubs, {
      type: 'pathToId',
      allFilePathOfVersion,
    });
  }
  if (test.data.fixedAddrs) {
    if (test.data.fixedAddrs.exprs) {
      test.data.fixedAddrs.exprs = handleIntegrationVariables.handleFixedAddressExprs(test.data.fixedAddrs.exprs, {
        type: 'pathToId',
        allFilePathOfVersion,
        allFuncsOfFileAndMangledId,
      });
    }
    if (test.data.fixedAddrs.bases) {
      test.data.fixedAddrs.bases = handleIntegrationVariables.handleFixedAddressBases(test.data.fixedAddrs.bases, {
        type: 'pathToId',
        allFilePathOfVersion,
      });
    }
  }
  // 处理输出检查项
  if (test.data.output) {
    if (test.data.output.global) {
      test.data.output.global = handleIntegrationVariables.handleGlobals(test.data.output.global, {
        type: 'pathToId',
        allFilePathOfVersion,
      });
    }
    if (test.data.output.statics) {
      test.data.output.statics = handleIntegrationVariables.handleStatics(test.data.output.statics, {
        type: 'pathToId',
        allFilePathOfVersion,
        allFuncsOfFileAndMangledId,
      });
    }
    if (test.data.output.malloc) {
      test.data.output.malloc = handleIntegrationVariables.handleMallocs(test.data.output.malloc, {
        type: 'pathToid',
        allFilePathOfVersion,
      });
    }
    if (test.data.output.fixedAddrs) {
      delete test.data.output.fixedAddrs.bases;
    }
  }

  return test;
}
/**
 * 删除测试用例中冗余的变量数据
 * @param {Object} testcase 测试用例数据
 * @param {Object} functionVariables 函数变量数据
 */
function deleteRedundantVariablesOfTestcase(testcase, newFuncVariables, originalFuncVariables) {
  if (!testcase.data) {
    return;
  }
  const {
    variables: testVariables = {}, stubs: testStubs, fixedAddrs: testFixedAddrs = {}, output: testOutput = {},
  } = testcase.data;
  const {
    variables: funcVariables = {}, stubs: funcStubs = {}, fixedAddrs: funcFixedAddres = {}, output: funcOutput = {},
  } = newFuncVariables;
  const { fixedAddrs: originalFuncFixedAddres = {}, stubs: originalFuncStubs = {}, output: originalOutput } = originalFuncVariables;
  const collectGlobalSet = new Set();
  const collectStubsSet = new Set();
  const collectStaticsSet = new Set();
  const collectParamsSet = new Set();
  const collectFixedAddresExprsSet = new Set();
  const collectFixedAddresBasesSet = new Set();
  // 收集全局变量
  if (originalFuncVariables.variables && originalFuncVariables.variables.global) {
    Object.keys(originalFuncVariables.variables.global).forEach((globalName) => {
      if (originalFuncVariables.variables.global[globalName].selfDefined) {
        collectGlobalSet.add(globalName);
      }
    });
  }
  if (funcVariables.global) {
    Object.keys(funcVariables.global).forEach((globalName) => {
      collectGlobalSet.add(globalName);
    });
  }
  function getStubUniquekey(stub) {
    let key = '';
    if (stub['%'] && stub['%']['@type']) {
      key = stub['%']['@type'];
    }
    if (stub.params) {
      stub.params.forEach((param) => {
        if (!param['@type']) {
          return;
        }
        key += `_${param['@type']}`;
      });
    }
    return key;
  }
  // 收集桩函数
  Object.keys(originalFuncStubs).forEach((stubName) => {
    if (originalFuncStubs[stubName].stubsTriggerKind === stubTriggerKind.manual) {
      collectStubsSet.add(stubName);
    }
  });
  Object.keys(funcStubs).forEach((stubName) => {
    const originalStub = originalFuncStubs[stubName];
    const newStub = funcStubs[stubName];
    // 桩函数的对比需要把返回值类型和形参类型还有个数也考虑进去
    if (originalStub && getStubUniquekey(newStub) !== getStubUniquekey(originalStub)) {
      return;
    }
    collectStubsSet.add(stubName);
  });

  // 收集局部静态变量
  if (funcVariables.statics) {
    Object.keys(funcVariables.statics).forEach((staticName) => {
      collectStaticsSet.add(staticName);
    });
  }

  // 收集形参
  if (funcVariables.params) {
    Object.keys(funcVariables.params).forEach((paramName) => {
      collectParamsSet.add(paramName);
    });
  }

  // 收集绝对地址表达式
  if (funcFixedAddres.exprs) {
    Object.keys(funcFixedAddres.exprs).forEach((exprsName) => {
      collectFixedAddresExprsSet.add(exprsName);
    });
  }

  // 收集绝对地址目标
  if (funcFixedAddres.bases) {
    Object.keys(funcFixedAddres.bases).forEach((baseName) => {
      collectFixedAddresBasesSet.add(baseName);
    });
  }
  if (originalFuncFixedAddres.bases) {
    Object.keys(originalFuncFixedAddres.bases).forEach((baseName) => {
      collectFixedAddresBasesSet.add(baseName);
    });
  }
  const enableVariables = {
    global: collectGlobalSet,
    stubs: collectStubsSet,
    statics: collectStaticsSet,
    params: collectParamsSet,
    bases: collectFixedAddresBasesSet,
    exprs: collectFixedAddresExprsSet,
  };
  // testcase input variables
  ['global', 'statics', 'params'].forEach((keyName) => {
    if (!testVariables[keyName]) {
      return;
    }
    Object.keys(testVariables[keyName]).forEach((childKeyName) => {
      if (!enableVariables[keyName].has(childKeyName)) {
        delete testVariables[keyName][childKeyName];
      }
    });
    testcase.data.variables[keyName] = testVariables[keyName];
  });
  // funcFixedAddres exprs
  if (testFixedAddrs.exprs) {
    Object.keys(testFixedAddrs.exprs).forEach((exprsName) => {
      if (!enableVariables.exprs.has(exprsName)) {
        delete testFixedAddrs.exprs[exprsName];
      }
    });
    testcase.data.fixedAddrs.exprs = testFixedAddrs.exprs;
  }
  // handle fixedAddrs exprs of variables
  if (originalFuncFixedAddres.exprs) {
    Object.keys(originalFuncFixedAddres.exprs).forEach((exprsName) => {
      if (!enableVariables.exprs.has(exprsName)) {
        delete originalFuncFixedAddres.exprs[exprsName];
      }
    });
    originalFuncVariables.fixedAddrs.exprs = originalFuncFixedAddres.exprs;
  }
  // fixedAddr input bases
  if (testFixedAddrs.bases) {
    Object.keys(testFixedAddrs.bases).forEach((baseName) => {
      if (!enableVariables.bases.has(baseName)) {
        delete testcase.data.fixedAddrs.bases[baseName];
      }
    });
  }
  // input stubs
  if (testStubs) {
    Object.keys(testStubs).forEach((stubName) => {
      if (!enableVariables.stubs.has(stubName)) {
        delete testStubs[stubName];
      }
    });
    testcase.data.stubs = testStubs;
  }
  // output
  ['global', 'statics', 'stubs'].forEach((key) => {
    if (!testOutput[key]) {
      return;
    }
    Object.keys(testOutput[key]).forEach((childKeyName) => {
      if (!enableVariables[key].has(childKeyName)) {
        delete testOutput[key][childKeyName];
      }
    });
    testcase.data.output[key] = testOutput[key];
  });
  // fixedAddr output bases
  if (testOutput.fixedAddrs) {
    Object.keys(testOutput.fixedAddrs).forEach((baseName) => {
      if (!enableVariables.bases.has(baseName)) {
        delete testcase.data.output.fixedAddrs[baseName];
      }
    });
  }
  if (originalOutput && originalOutput.stubs) {
    Object.keys(originalOutput.stubs).forEach((stubName) => {
      if (!enableVariables.stubs.has(stubName)) {
        delete originalOutput.stubs[stubName];
      }
    });
  }
  // return
  if (testOutput['%'] && !funcOutput['%']) {
    delete testcase.data.output['%'];
  }
}
/**
 * 处理测试用例断点导入
 * @param {String|ObjetId} testcaseId  测试用例id
 * @param {Object} {
 *  versionType,
    entryFunc,// 入口函数
    allFilePathOfVersion,
    allFuncsOfFileAndMangledId,
    breakPointsData,
 * }
 */
async function handleTestceseBreakPointsImport(testcaseId, {
  versionType,
  entryFunc,
  allFilePathOfVersion,
  allFuncsOfFileAndMangledId,
  breakPointsData = [],
}) {
  await BreakPoint.deleteMany({ testcaseId });
  return utility.arrayChunkOperation(breakPointsData, 1, async (dp) => {
    dp.entryFunctionId = entryFunc._id;
    dp.testcaseId = testcaseId;
    const file = allFilePathOfVersion.get(dp.filePath);
    if (!file) {
      return;
    }
    const func = allFuncsOfFileAndMangledId.get(`${file._id.toString()}-${dp.mangledId}`);
    if (!func) {
      return;
    }
    dp.fileId = file._id;
    dp.functionId = func._id;
    dp.versionId = entryFunc.versionId;
    await breakPoints.upsertBreakPoint(null, dp, versionType);
  });
}
/**
 * 处理单用例的导入
 * @param {object} file 当前文件
 * @param {object} func 当前函数
 * @param {object} testCase 测试用例
 * @param {object} allFilePathOfVersion 版本所有文件
 * @param {object} allFuncsOfFileAndMangledId 版本所有函数
 * @param {object} importedTestsData 已经导入成功的用例
 * @param {object} newTestCases 导入之后返回的用例数据
 * @param {Set} updatedTestCases 更新的测试用例数据
 * @param {String} versionType 当前版本类型
 * @param {Object} originalFunctionVariables 测试用例导入的原始的variables
 * @param {Map} importedTestIds 曾经被导入过的原始用例Id
 * @param {Number} versionTestcaseCount 版本测试用例个数
 */
async function handleSingleTestCaseImport(
  file,
  func,
  testCase,
  allFuncsOfFileAndMangledId,
  allFilePathOfVersion,
  importedTestsData,
  newTestCases,
  updatedTestCases,
  versionType,
  originalFunctionVariables,
  importedTestIds,
  versionTestcaseCount
) {
  const functionId = func._id.toString();
  testCase.data = testCase.data || {};
  // 检查测试用例形参名称变化
  if (originalFunctionVariables
    && originalFunctionVariables.variables
    && originalFunctionVariables.variables.params
    && testCase.data.variables
    && testCase.data.variables.params
    && func.variables.variables.params) {
    const origialFunctionParams = originalFunctionVariables.variables.params;
    const functionNewParams = func.variables.variables.params;
    Object.keys(origialFunctionParams).forEach((originalParamName) => {
      let newName;
      Object.keys(functionNewParams).forEach((newParamName) => {
        if (functionNewParams[newParamName]['@index']
          && origialFunctionParams[originalParamName]['@index']
          && functionNewParams[newParamName]['@type']
          && origialFunctionParams[originalParamName]['@type']
          && functionNewParams[newParamName]['@index'] === origialFunctionParams[originalParamName]['@index']
          && functionNewParams[newParamName]['@type'] === origialFunctionParams[originalParamName]['@type']
          && newParamName !== originalParamName) {
          newName = newParamName;
        }
      });
      if (newName && testCase.data.variables.params[originalParamName]) {
        testCase.data.variables.params[newName] = testCase.data.variables.params[originalParamName];
        delete testCase.data.variables.params[originalParamName];
      }
    });
  }
  if (versionType === projectVersions.versionType.integrationTest) {
    // 处理导入测试用例中的变量文件路径
    testCase = await replaceImportTestCasePath(
      testCase,
      allFilePathOfVersion,
      allFuncsOfFileAndMangledId,
      originalFunctionVariables,
      func.variables
    );
  } else {
    // 单元测试删除测试用例中冗余的变量信息
    deleteRedundantVariablesOfTestcase(testCase, func.variables, originalFunctionVariables);
  }
  if (!importedTestsData[functionId]) {
    importedTestsData[functionId] = [];
  }
  // update stubs times
  if (testCase.data && testCase.data.stubs) {
    Object.keys(testCase.data.stubs).forEach((stubName) => {
      if (testCase.data.stubs[stubName]['@value'] && Object.keys(testCase.data.stubs[stubName]['@value']).length > 0 && !testCase.data.stubs[stubName].times) {
        testCase.data.stubs[stubName].times = parseInt((Object.keys(testCase.data.stubs[stubName]['@value']).slice(-1)), 10);
      }
    });
  }
  // 去掉测试用例中绝对地址表达式中旧文件信息
  if (testCase.data && testCase.data.fixedAddrs && testCase.data.fixedAddrs.exprs) {
    Object.keys(testCase.data.fixedAddrs.exprs).forEach((exprName) => {
      delete testCase.data.fixedAddrs.exprs[exprName].fileId;
      delete testCase.data.fixedAddrs.exprs[exprName].filePath;
    });
  }
  testCase.versionType = versionType;
  importedTestsData[functionId].push({ fileId: file._id, ...testCase.data });
  // 版本内已经新建了测试用例的测试用例标识需要重新生成
  if (versionTestcaseCount > 0) {
    delete testCase.identifierNumber;
  }
  let newTestcaseId = testCase._id;
  if (testCase._id && importedTestIds.has(testCase._id.toString())) {
    // 设置标识测试用例的更新是通过上传方式
    testCase.testcaseImport = true;
    await updateTest(importedTestIds.get(testCase._id.toString()), testCase);
    updatedTestCases.add(testCase._id.toString());
  } else {
    const resTest = await addTest(
      func.projectId,
      func.versionId,
      func.fileId,
      functionId,
      testCase
    );
    newTestcaseId = resTest._id;
    newTestCases.push({
      originId: testCase._id,
      newId: resTest._id.toString(),
    });
  }
  // 处理测试用例断点导入
  if (testCase.breakPoints) {
    await handleTestceseBreakPointsImport(newTestcaseId, {
      versionType,
      entryFunc: func,
      allFilePathOfVersion,
      allFuncsOfFileAndMangledId,
      breakPointsData: testCase.breakPoints,
    });
  }
  return { number: testCase.number, imported: true };
}
function handleTestFieldConfigNumericalBasesPath(numericalBases, allFilePathOfVersion, allFuncsOfFileAndMangledId) {
  if (utility.isEmpty(numericalBases)) {
    return;
  }
  Object.keys(numericalBases).forEach((numericalBaseKey) => {
    const matchKeys = [...numericalBaseKey.matchAll(/\$([^$]+)\$/g)];
    let newNumericalBaseKey = numericalBaseKey;
    for (const matchKey of matchKeys) {
      const [, matchKeyIndex1] = matchKey;
      let replaceTargetKey;
      let mangledId;
      let filePath;
      let file;
      let func;
      if (matchKeyIndex1.indexOf('@')) {
        [filePath, mangledId] = matchKeyIndex1.split('@');
      } else {
        filePath = matchKeyIndex1;
      }
      if (allFilePathOfVersion.has(filePath)) {
        file = allFilePathOfVersion.get(filePath);
      }
      if (!file) {
        delete numericalBases[numericalBaseKey];
        break;
      }
      replaceTargetKey = file._id.toString();
      if (mangledId) {
        func = allFuncsOfFileAndMangledId.get(`${file._id.toString()}-${mangledId}`);
      }
      if (mangledId && !func) {
        delete numericalBases[numericalBaseKey];
        break;
      }
      if (func) {
        replaceTargetKey = func._id.toString();
      }
      newNumericalBaseKey = newNumericalBaseKey.replace(new RegExp(`\\$${matchKeyIndex1}\\$`, 'g'), replaceTargetKey);
    }
    newNumericalBaseKey = newNumericalBaseKey.replace(/\|/g, '.');
    numericalBases[newNumericalBaseKey] = numericalBases[numericalBaseKey];
    delete numericalBases[numericalBaseKey];
  });
}
/**
 * 导入单函数测试用例
 * @param {Object} file 当前文件
 * @param {Object} func 当前函数
 * @param {Object} testCasesData 测试用例
 * @param {String} versionType 版本类型
 * @param {Map} allFilePathOfVersion 版本所有文件
 * @param {Map} allFuncsOfFileAndMangledId 版本所有函数
 * @param {Map} allFileMap 版本所有文件
 * @param {Array<String>} sourceFileExts 源文件后缀
 * @param {Map} udfs 用户自定义字段
 * @param {Object} importedTestsData 已经导入成功的用例
 * @param {Object} importedFunctions 已经导入成功的用例的函数
 * @param {Boolean} isRegressionReusedCases 是否回归测试复用用例
 * @returns {Promise}
 */
async function handleFunctionTestCaseImport(
  file,
  func,
  testCasesData,
  version,
  allFilePathOfVersion,
  allFuncsOfFileAndMangledId,
  allFileMap,
  sourceFileExts,
  udfs,
  importedTestsData,
  importedFunctions,
  isRegressionReusedCases = false,
  versionTestcaseCount
) {
  let testCases = [];
  let testCaseGroups = [];
  let originalFunctionVariables;
  let importedInvokeRelations;
  const { versionType } = version;
  const newTestCases = [];
  const updatedTestCases = new Set();
  // 兼容老的测试用例导入结构
  if (utility.isObjectType(testCasesData)
    && testCasesData.testCases
    && testCasesData.testCaseGroups) {
    testCases = testCasesData.testCases;
    testCaseGroups = testCasesData.testCaseGroups;
    originalFunctionVariables = testCasesData.functionVariables;
    importedInvokeRelations = testCasesData.invokeRelations;
  } else {
    testCases = testCasesData;
  }
  const testCaseImportResult = [];
  // 处理函数的绝对地址
  await functions.IntegrationAddInvokeFuncFixedAddressBases(func.variables, sourceFileExts, versionType, allFileMap);
  const importedTestIds = await TestCase.find({ functionId: mongoose.Types.ObjectId(func._id) }, ['originalImportedId']);
  const importedTestIdsMap = new Map();
  importedTestIds.forEach((item) => {
    importedTestIdsMap.set(item.originalImportedId ? item.originalImportedId.toString() : item._id.toString(), item._id);
  });
  let importedCount = 0;
  const failedImportTestCases = [];
  const existingTestCaseCount = await TestCase.countDocuments({ functionId: func._id });
  if (testCasesData.testFieldConfig) {
    // 没有测试用例的函数需要初始化配置
    await initFunctionTestFieldConfig(func._id, version);
    if (!utility.isEmpty(testCasesData.testFieldConfig.numericalBases)) {
      handleTestFieldConfigNumericalBasesPath(testCasesData.testFieldConfig.numericalBases, allFilePathOfVersion, allFuncsOfFileAndMangledId);
    }
    await updateTestFieldConfig(func._id, testCasesData.testFieldConfig);
  }
  await utility.arrayChunkOperation(testCases, 50, async (testCase) => {
    // 删除output.exceptions 执行用例的时候重新组装exceptions
    if (testCase.data && testCase.data.output && testCase.data.output.exceptions) {
      delete testCase.data.output.exceptions;
    }
    if (isRegressionReusedCases) {
      testCase.sourceInRegression = 0;
    }
    try {
      const importedCase = await handleSingleTestCaseImport(
        file,
        func,
        testCase,
        allFuncsOfFileAndMangledId,
        allFilePathOfVersion,
        importedTestsData,
        newTestCases,
        updatedTestCases,
        versionType,
        originalFunctionVariables,
        importedTestIdsMap,
        versionTestcaseCount
      );
      testCaseImportResult.push(importedCase);
    } catch (error) {
      logger.debug(`${file.path} ${func.functionName} ${func.mangledId} test case number:${testCase.number} import failed by ${error}`);
      testCaseImportResult.push({ number: testCase.number, imported: false });
    }
  });
  testCaseImportResult.forEach((r) => {
    if (r.imported) {
      importedCount += 1;
      return;
    }
    failedImportTestCases.push(r.number);
  });
  if (originalFunctionVariables && importedCount > 0) {
    importedFunctions[func._id.toString()] = { ...originalFunctionVariables, fileId: file._id, existingTestCaseCount };
  }
  // 更新调用关系
  if (importedInvokeRelations && importedCount > 0) {
    const importedRelationsMap = new Map();
    importedInvokeRelations.forEach((relation) => {
      if (!allFilePathOfVersion.has(relation.fromFilePath)) {
        return;
      }
      importedRelationsMap.set(
        `${allFilePathOfVersion.get(relation.fromFilePath)._id.toString()}:${relation.fromMangled}:${relation.toMangled}`,
        {
          toFilePath: relation.toFilePath,
          enableStub: relation.enableStub,
        }
      );
    });
    const currentInvokeRelations = await invokeRelation.retrieveRelations(func._id, { initRelations: true });
    const updateInvokes = [];
    currentInvokeRelations.forEach((relation) => {
      const key = `${relation.fromFuncFileId.toString()}:${relation.fromFuncMangledId}:${relation.toFuncMangledId}`;
      if (!importedRelationsMap.has(key)) {
        return;
      }
      const updateData = {};
      if (importedRelationsMap.get(key).enableStub !== relation.enableStub) {
        updateData.enableStub = importedRelationsMap.get(key).enableStub;
      }
      if (allFilePathOfVersion.has(importedRelationsMap.get(key).toFilePath)
        && allFilePathOfVersion.get(importedRelationsMap.get(key).toFilePath)._id.toString() !== relation.toFuncFileId.toString()) {
        updateData.toFuncFileId = allFilePathOfVersion.get(importedRelationsMap.get(key).toFilePath)._id;
      }
      if (Object.keys(updateData).length === 0) {
        return;
      }
      updateInvokes.push(
        {
          updateOne: {
            filter: { _id: relation._id },
            update: updateData,
          },
        }
      );
    });
    if (updateInvokes.length > 0) {
      await invokeRelation.bulkUpdateRelations(updateInvokes);
      await functions.updateInvokeCoverageTotal([func._id], func.versionId);
    }
  }
  logger.debug(`filePath:${file.path};functionName:${func.functionName} ${func.mangledId};import test case count:${testCases.length};import success:${importedCount},import failed:${failedImportTestCases.length};test case number of failed:${failedImportTestCases}`);

  if (testCaseGroups.length > 0) {
    await importGroupsByfunction(func, testCaseGroups, newTestCases);
  }
  if (updatedTestCases.size > 0) {
    await functions.updateFunctionCoverage(func._id);
    if (versionType === projectVersions.versionType.integrationTest) {
      await functions.updateFunctionInvokeCoverage(func._id);
    }
  }
  const updateFunc = {};
  // 更新自定义字段
  if (testCasesData.functionDesc) {
    updateFunc.description = testCasesData.functionDesc;
  }
  if (testCasesData.functionUdf) {
    testCasesData.functionUdf.forEach((importUdf) => {
      if (!udfs.has(importUdf.name)) {
        return;
      }
      importUdf.key = udfs.get(importUdf.name);
      if (!updateFunc.userDefinedFields) {
        updateFunc.userDefinedFields = [];
      }
      updateFunc.userDefinedFields.push(importUdf);
    });
  }
  if (Object.keys(updateFunc).length > 0) {
    await Func.updateOne({ _id: func._id }, { $set: updateFunc });
  }
  return importedTestsData;
}
function handleOutPutVariableFuncName(key, funcMap, fileMap) {
  let newKey;
  const filePath = key.substring(key.lastIndexOf('@') + 1, key.length);
  const file = fileMap.get(filePath);
  if (file) {
    const funcName = key.split('@')[1];
    const func = funcMap.get(`${file._id.toString()}-${funcName}`);
    [newKey] = key.split('@');
    if (func) {
      newKey += `@${func._id.toString()}`;
    }
  }
  return newKey;
}
async function formatTestCasesOutput(testCaseData, projectVersion, isTestCaseGroup) {
  const testCaseOutputMap = new Map();
  const runtimeOutputMap = new Map();
  const testCaseIdSet = new Set();
  if (!isTestCaseGroup) {
    testCaseIdSet.add(testCaseData.testcase);
  }
  function retrieveAllTests(testCaseGroupResult) {
    if (!utility.isArrayType(testCaseGroupResult) || testCaseGroupResult.length === 0) {
      return;
    }
    testCaseGroupResult.forEach((test) => {
      if (test.resultlist !== undefined) {
        retrieveAllTests(test.resultlist);
        return;
      }
      testCaseIdSet.add(test.testcase);
      if (!test.output) {
        return;
      }
      testCaseOutputMap.set(test.testcase, test.output);
      if (test.runtime) {
        runtimeOutputMap.set(test.testcase, test.runtime);
      }
    });
  }
  if (isTestCaseGroup) {
    retrieveAllTests(testCaseData.resultlist);
  } else if (testCaseData.output) {
    testCaseOutputMap.set(testCaseData.testcase, testCaseData.output);
    if (testCaseData.runtime) {
      runtimeOutputMap.set(testCaseData.testcase, testCaseData.runtime);
    }
  }
  if (projectVersion.versionType !== projectVersions.versionType.integrationTest) {
    return { testCaseOutputMap, runtimeOutputMap, testCaseIds: Array.from(testCaseIdSet) };
  }
  const invokeTimesMap = {};
  if (testCaseData.callstack) {
    utility.recursiveIterCallstack(testCaseData.callstack, (obj, key) => {
      invokeTimesMap[key] = obj.count;
    });
  }
  let test;
  if (!isTestCaseGroup) {
    test = await TestCase.findById(mongoose.Types.ObjectId(testCaseData.testcase));
  } else {
    test = await TestCaseGroup.findById(mongoose.Types.ObjectId(testCaseData.testcase));
  }
  const { functionId } = test;
  let currentFunc;
  const allFileMap = await fileModel.getSourceFileByVersionId(projectVersion._id, null, { _id: 1, path: 1 }, 'path');
  const allFuncMap = new Map();
  const funcs = await Func.find({ versionId: projectVersion._id, kind: { $in: fileElement.getCountableKinds() } }, {
    _id: 1, fileId: 1, functionName: 1, functionAttrs: 1, kind: 1, language: 1,
  });
  funcs.forEach((func) => {
    if (func._id.toString() === functionId.toString()) {
      currentFunc = func;
    }
    const key = `${func.fileId.toString()}-${func.functionName}`;
    if (!allFuncMap.has(key)) {
      allFuncMap.set(key, func);
    }
  });
  const variables = await functions.getRelationFuncsVariables(functionId);
  const { output } = variables;
  const variableStubs = Object.values(variables.stubs);
  if (output) {
    // 集成测试通过callstack获取调用次数@count，单元测试核心直接给
    testCaseOutputMap.forEach((testCaseOutput) => {
      Object.keys(invokeTimesMap).forEach((stubField) => {
        const [stubMangled] = stubField.match(/[\w]+/);
        const filePath = stubField.substring(stubField.lastIndexOf('@') + 1, stubField.length);
        const file = allFileMap.get(filePath);
        if (!file) {
          // 文件不存在，则忽略
          return;
        }
        const stubKey = `${stubMangled}@${file._id}`;
        if (!variableStubs.find((vs) => stubKey === `${vs['@mangled']}@${vs.fileId}`)) {
          // stubs里不存在，则忽略
          return;
        }
        // 把桩函数执行次数强行加到output.stubs中去,即使原本的output.stubs中不存在
        utility.setValueByKeyPathArray(testCaseOutput, ['stubs', stubField, '@count'], invokeTimesMap[stubField]);
      });
      Object.keys(testCaseOutput).forEach((key) => {
        switch (key) {
          case 'global': {
            Object.keys(testCaseOutput.global).forEach((globalKey) => {
              let funcVariableGlobalName;
              const filePath = globalKey.substring(globalKey.lastIndexOf('@') + 1, globalKey.length);
              const globalName = globalKey.substring(0, globalKey.lastIndexOf('@'));
              const file = allFileMap.get(filePath);
              if (!file) {
                return;
              }
              if (output.global[`${globalName}@${file._id.toString()}`]) {
                funcVariableGlobalName = `${globalName}@${file._id.toString()}`;
              } else if (output.global[globalName]) {
                funcVariableGlobalName = globalName;
              }
              if (funcVariableGlobalName) {
                testCaseOutput.global[funcVariableGlobalName] = testCaseOutput.global[globalKey];
              }
              delete testCaseOutput.global[globalKey];
            });
            break;
          }
          case 'malloc': {
            Object.keys(testCaseOutput.malloc).forEach((mallocKey) => {
              const mallocName = mallocKey.substring(0, mallocKey.lastIndexOf('@'));
              if (output.malloc[mallocName]) {
                testCaseOutput.malloc[mallocName] = testCaseOutput.malloc[mallocKey];
              }
              delete testCaseOutput.malloc[mallocKey];
            });
            break;
          }
          case 'statics': {
            Object.keys(testCaseOutput.statics).forEach((staticKey) => {
              const newKey = handleOutPutVariableFuncName(staticKey, allFuncMap, allFileMap);
              testCaseOutput.statics[newKey] = testCaseOutput.statics[staticKey];
              delete testCaseOutput.statics[staticKey];
            });
            break;
          }
          case 'unused': {
            Object.keys(testCaseOutput.unused).forEach((key) => {
              switch (key) {
                case 'statics': {
                  const newUnusedStatics = [];
                  testCaseOutput.unused.statics.forEach((staticKey) => {
                    newUnusedStatics.push(handleOutPutVariableFuncName(
                      staticKey, allFuncMap, allFileMap
                    ));
                  });
                  testCaseOutput.unused.statics = newUnusedStatics;
                  break;
                }
                default: break;
              }
            });
            break;
          }
          case 'fixedAddrs': {
            Object.keys(testCaseOutput.fixedAddrs).forEach((fixedAddrKey) => {
              const filePath = fixedAddrKey.split('@')[1];
              const file = allFileMap.get(filePath);
              if (file) {
                const newKey = fixedAddrKey.split('@')[0];
                testCaseOutput.fixedAddrs[newKey] = testCaseOutput.fixedAddrs[fixedAddrKey];
              }
              delete testCaseOutput.fixedAddrs[fixedAddrKey];
            });
            break;
          }
          case 'stubs': {
            const variableStubMangleId = new Map();
            Object.keys(variables.stubs).forEach((stubKey) => {
              const variableStub = variables.stubs[stubKey];
              variableStubMangleId.set(variableStub['@mangled'], variableStub['@name']);
            });

            Object.keys(testCaseOutput.stubs).forEach((stubField) => {
              if (!stubField.match(/[\w]+/)) {
                return;
              }
              const [stubMangled] = stubField.match(/[\w]+/);
              const filePath = stubField.substring(stubField.lastIndexOf('@') + 1, stubField.length);
              const file = allFileMap.get(filePath);
              if (!file || !variableStubMangleId.has(stubMangled)) {
                delete testCaseOutput.stubs[stubField];
                return;
              }
              // 将集成函数检查项过滤出来
              if (output.integratedFuncs && output.integratedFuncs[stubMangled]) {
                testCaseOutput.integratedFuncs = testCaseOutput.integratedFuncs || {};
                testCaseOutput.integratedFuncs[stubMangled] = testCaseOutput.stubs[stubField];
                delete testCaseOutput.stubs[stubField];
                return;
              }
              // 在stubs中：c语言使用函数名做key，c++使用mangleid做key
              let newStubName = functions.isCFunction(currentFunc) ? variableStubMangleId.get(stubMangled) : stubMangled;
              // 处理拼接文件问题
              const fileStubKey = `${newStubName}@${file._id.toString()}`;
              if (variables.stubs[fileStubKey]) {
                newStubName = fileStubKey;
              }
              if (!testCaseOutput.stubs[newStubName]) {
                testCaseOutput.stubs[newStubName] = {};
                testCaseOutput.stubs[newStubName] = testCaseOutput.stubs[stubField];
                delete testCaseOutput.stubs[stubField];
              }
            });
            if (Object.keys(testCaseOutput.stubs).length === 0) {
              delete testCaseOutput.stubs;
            }
            break;
          }
          default: break;
        }
      });
    });
  }
  return {
    testCaseOutputMap,
    runtimeOutputMap,
    testCaseIds: Array.from(testCaseIdSet),
  };
}
async function initFunctionTestFieldConfig(functionId, version) {
  const func = await Func.findById(functionId);
  if (!func) {
    throw new PhoenixError('not found', `functionId:${functionId} not exist.`);
  }
  let defaults = await TestCaseFieldConfig.findOne({ functionId }).lean();
  if (defaults) {
    return defaults;
  }
  const variableType = [];
  const fields = [
    'global',
    'param',
    'mallocVariable',
    'stub',
    'fixedAddressExpression',
    'statics',
  ];
  if (!version) {
    version = await ProjectVersion.findById(func.versionId);
  }
  if (version.versionType === projectVersions.versionType.integrationTest) {
    fields.push('integratedFunc');
  }
  fields.forEach((key) => {
    variableType.push(functions.variableKind[key]);
  });
  defaults = {
    functionId,
    dataDepth: version.dataDepth,
    dataType: Object.values(functions.variableScopeKind),
    variableType,
    displaySetting: '0',
    numericalBases: {},
    foldPattern: 1, // 数据表层级折叠方式
  };
  await (new TestCaseFieldConfig({
    ...defaults,
    functionId: func._id,
    fileId: func.fileId,
    projectId: func.projectId,
    versionId: func.versionId,
    creatorId: func.creatorId,
  })).save();
  return defaults;
}

async function getTestFieldConfig(functionIds, version,
  fields = ['dataDepth', 'dataType', 'variableType', 'displaySetting', 'foldPattern', 'numericalBases']) {
  fields.push('functionId');
  const resTestFieldConfigs = [];
  if (functionIds && !utility.isArrayType(functionIds)) {
    functionIds = [].concat(functionIds);
  }
  const testFieldConfigs = await TestCaseFieldConfig.find({ functionId: { $in: functionIds } }, fields).lean();
  const initedTestFieldFunctionIdsMap = new Map();
  testFieldConfigs.forEach((testFieldConfig) => {
    testFieldConfig.numericalBases = testFieldConfig.numericalBases ? Object.keys(testFieldConfig.numericalBases).reduce((obj, key) => {
      const val = testFieldConfig.numericalBases[key];
      const fieldPth = key.replace(/\|/g, '.');
      obj[fieldPth] = val;
      return obj;
    }, {}) : {};
    initedTestFieldFunctionIdsMap.set(testFieldConfig.functionId.toString(), testFieldConfig);
  });
  await utility.arrayChunkOperation(functionIds, 30, async (functionId) => {
    functionId = functionId.toString();
    if (initedTestFieldFunctionIdsMap.has(functionId)) {
      resTestFieldConfigs.push(initedTestFieldFunctionIdsMap.get(functionId));
      return;
    }
    const defaultTestFieldConfig = await initFunctionTestFieldConfig(functionId, version);
    resTestFieldConfigs.push(defaultTestFieldConfig);
  });
  return resTestFieldConfigs;
}
async function getTestFieldConfigByVerisonId(versionId) {
  const version = await ProjectVersion.findById(versionId);
  const funcs = await Func.find({ versionId, kind: { $in: fileElement.getCountableKinds() } }).lean();
  return getTestFieldConfig(funcs.map((func) => func._id), version);
}
async function updateTestFieldConfig(functionId, doc) {
  functionId = mongoose.Types.ObjectId(functionId);
  const func = await Func.findById(functionId);
  if (!func) {
    return null;
  }
  if (doc.numericalBases) {
    const newNumericalBases = {};
    // 显示进制设置，.替换为|
    Object.keys(doc.numericalBases).forEach((key) => {
      const basesSet = new Set([10, 16, 8, 'char']);
      const val = doc.numericalBases[key];
      if (!basesSet.has(val)) {
        throw new PhoenixError('invalid request', `invalid numerical base: ${doc.numericalBases[key]}`);
      }
      const fieldPth = key.replace(/\./g, '|');
      newNumericalBases[fieldPth] = val;
    });
    const oldConfig = await TestCaseFieldConfig.findOne({ functionId }, 'numericalBases').lean();
    if (oldConfig && oldConfig.numericalBases) {
      const changedNumericalBases = {};
      Object.keys(doc.numericalBases).forEach((key) => {
        const oldKey = key.replace(/\./g, '|');
        if (!oldConfig.numericalBases[oldKey] || oldConfig.numericalBases[oldKey] !== doc.numericalBases[key]) {
          changedNumericalBases[key] = doc.numericalBases[key];
        }
      });
      await updateTestFieldBase(functionId, changedNumericalBases);
    } else {
      await updateTestFieldBase(functionId, doc.numericalBases);
    }
    delete doc.numericalBases;
    doc.numericalBases = newNumericalBases;
  }
  const newTestFieldConfig = await TestCaseFieldConfig.findOneAndUpdate(
    {
      functionId,
    },
    {
      $set: doc,
    },
    {
      upsert: true,
      setDefaultsOnInsert: true,
      new: true,
      lean: true,
    }
  );
  return {
    functionId: newTestFieldConfig.functionId,
    dataDepth: newTestFieldConfig.dataDepth,
    dataType: newTestFieldConfig.dataType,
    variableType: newTestFieldConfig.variableType,
    displaySetting: newTestFieldConfig.displaySetting,
    numericalBases: newTestFieldConfig.numericalBases ? Object.keys(newTestFieldConfig.numericalBases).reduce((obj, key) => {
      const val = newTestFieldConfig.numericalBases[key];
      const fieldPth = key.replace(/\|/g, '.');
      obj[fieldPth] = val;
      return obj;
    }, {}) : {},
    foldPattern: newTestFieldConfig.foldPattern,
  };
}
async function updateTestFieldBase(functionId, basesMap) {
  const opts = {
    functionId,
    // $or: Object.keys(basesMap).map((key) => {
    //   const fieldPth = `data.${key}`;
    //   return {
    //     [fieldPth]: { $exists: 1 },
    //   };
    // }),
  };
  const testCases = await TestCase.find(opts).lean();
  const bulkWrites = [];
  testCases.forEach((tc) => {
    const setObj = {};
    Object.keys(basesMap).forEach((key) => {
      const fieldPth = `data.${key}`;
      const resultFieldPth = `data.${key.slice(0, -'value'.length)}result`;
      const val = utility.getValueByKeyPath(tc, fieldPth);
      const resultVal = utility.getValueByKeyPath(tc, resultFieldPth);
      const baseTo = basesMap[key];
      // console.log(key);
      if (!key.startsWith('output.')) { // 如果是输出，预期值不更新
        if (val !== null && val !== undefined && val !== '') {
          setObj[fieldPth] = valToBase(val, baseTo);
        }
      }
      if (resultVal !== null && resultVal !== undefined && resultVal !== '') {
        setObj[resultFieldPth] = valToBase(resultVal, baseTo);
      }
    });
    if (Object.keys(setObj).length) {
      bulkWrites.push({
        updateOne: {
          filter: { _id: tc._id },
          update: { $set: setObj },
        },
      });
    }
  });
  // console.log(bulkWrites);
  await TestCase.bulkWrite(bulkWrites);
}

/**
 *
 * @param {number|string} val
 * @param {2|8|10|16|'char'} baseTo
 * @returns {string}
 */
function valToBase(val, baseTo) {
  let numberOrBigInt;
  if (typeof val === 'number') {
    numberOrBigInt = val;
  } else if (typeof val === 'string' && utility.isSingleQuotedChar(val)) {
    switch (val) {
      case "'\\a'":
        numberOrBigInt = 7;
        break;
      default:
        numberOrBigInt = utility.parseSingleQuotedChar(val).charCodeAt(0);
        break;
    }
  } else if (typeof val === 'string' && val.trim().length) { // 判断length过滤掉空字符串的情况
    numberOrBigInt = utility.string2BigInt(val); // 普通数字字符串转为BigInt
  } else {
    logger.warn(`expect number type, but value is ${typeof val}: ${val}`);
  }
  if (baseTo === 'char') {
    switch (Number(numberOrBigInt)) {
      case 7:
        return "'\\a'";
      default:
        break;
    }
    const c = String.fromCharCode(Number(numberOrBigInt));
    if (JSON.stringify(c).indexOf('\\u') >= 0) {
      return `'\\x${numberOrBigInt.toString(16).padStart(2, '0')}'`;
    }
    // return `'${c}'`;
    const jsonStr = JSON.stringify(c);
    return `'${jsonStr.slice(1, jsonStr.length - 1)}'`; // JSON.stringify之后把双引号转为单引号
  }
  return utility.toBase(numberOrBigInt, baseTo);
}

async function clearTestcaseCoveredByFunc(functionId) {
  await TestCase.updateMany({ functionId: mongoose.Types.ObjectId(functionId) },
    {
      $set: {
        statementCovered: 0,
        branchCovered: 0,
        conditionCovered: 0,
        decisionCovered: 0,
        mcdcCovered: 0,
        invokeCovered: 0,
        invokedFuncCovered: 0,
      },
    });
}
/**
 * 导入单个文件的测试用例
 * @param {Object} file 当前文件
 * @param {String} testCaseFilePath 测试用例文件路径
 * @param {String} fileExt 文件后缀
 * @param {String} versionType 版本类型
 * @param {Map} allFilePathOfVersion 版本所有文件
 * @param {Map} allFuncsOfFileAndMangledId 版本所有函数
 * @param {Map} allFileMap 版本所有文件
 * @param {Array<String>} sourceFileExts 源文件后缀
 * @param {Map} udfs 用户自定义字段
 * @param {Object} importedTestsData 已经导入成功的用例
 * @param {Object} importedFunctions 已经导入成功的用例的函数
 * @param {Boolean} isRegressionReusedCases 是否回归测试复用用例
 * @param {Object} exportFuncFile 按函数导出，测试用例按函数分割
 * @param {Number} versionTestcaseCount 版本内的测试用例个数
 * @return {Promise}
 */
async function handleFileTestCaseImport(
  file,
  testCaseFilePath,
  fileExt,
  version,
  allFilePathOfVersion,
  allFuncsOfFileAndMangledId,
  allFileMap,
  sourceFileExts,
  udfs,
  importedTestsData,
  importedFunctions,
  isRegressionReusedCases = false,
  exportFuncFile,
  versionTestcaseCount
) {
  let testsData = {};
  // 读取Excel表格用例
  if (fileExt === 'xlsx') {
    const files = await fs.readdir(testCaseFilePath);
    await utility.arrayChunkOperation(files, 10, async (fileName) => {
      const funcTests = await readTestCasesByHuiCheng(path.join(testCaseFilePath, fileName));
      Object.assign(testsData, funcTests);
    });
  } if (exportFuncFile) {
    const files = await fs.readdir(testCaseFilePath);
    await utility.arrayChunkOperation(files, 10, async (fileName) => {
      const funcTests = await fs.readJson(path.join(testCaseFilePath, fileName));
      Object.assign(testsData, funcTests);
    });
  } else {
    testsData = await fs.readJson(testCaseFilePath);
  }
  await utility.arrayChunkOperation(Object.keys(testsData), 50, async (mangledId) => {
    // 兼容函数名做key的老的用例导出格式
    const func = file.functionMap.get(mangledId) || file.functionMap.get(file.funcNameToMangleId.get(mangledId));
    if (!func) {
      return;
    }
    await handleFunctionTestCaseImport(
      file,
      func,
      testsData[mangledId],
      version,
      allFilePathOfVersion,
      allFuncsOfFileAndMangledId,
      allFileMap,
      sourceFileExts,
      udfs,
      importedTestsData,
      importedFunctions,
      isRegressionReusedCases,
      versionTestcaseCount
    );
  });
  return importedTestsData;
}

async function format614TestCase(
  allVariableMap,
  allFileTypeMap,
  testCase,
  versionType
) {
  const {
    functionId, fileId, _id: testCaseId, userDefinedFields,
  } = testCase;
  let transformer;
  if (versionType === projectVersions.versionType.unitTest) {
    transformer = new TestCaseTransformer(
      testCase,
      allVariableMap.get(functionId.toString()),
      allFileTypeMap.get(testCase.fileId.toString())
    );
  } else {
    const variable = allVariableMap.get(functionId.toString());
    const fileIds = await getFilesByTestId(null, testCase, variable);
    const fileTypes = {};
    fileIds.forEach((fileId) => {
      fileId = fileId.toString();
      if (allFileTypeMap.has(fileId)) {
        fileTypes[fileId] = allFileTypeMap.get(fileId).types;
      }
    });
    transformer = new IntegrationTestCaseTransformer(testCase, variable, fileTypes);
  }
  transformer.transform();
  const returnValueData = {};
  function getReturnFieldPath(datas) {
    datas.forEach((item) => {
      if (!item.fieldPath) {
        return;
      }
      returnValueData[item.fieldPath] = {
        name: item.name,
        typeName: item.typeName,
      };
      if (item.children && item.children.length > 0) {
        getReturnFieldPath(item.children);
      }
    });
  }
  getReturnFieldPath(transformer.returnValueData);
  return {
    testCaseId,
    functionId,
    fileId,
    userDefinedFields,
    input: {
      global: transformer.globalVariablesData,
      stubs: transformer.stubsData,
      params: transformer.paramVariablesData,
      statics: transformer.staticsData,
      malloc: transformer.mallocVariablesData,
      fixedAddressBases: transformer.fixedAddressBasesData,
      fixedAddressExpressions: transformer.fixedAddressExpressionsData,
    },
    output: {
      global: transformer.globalOutputData,
      statics: transformer.staticsOutputData,
      malloc: transformer.mallocOutputData,
    },
    returnValueData,
  };
}
/**
 * 处理数组614数组变量
 * @param {string} variable 变量名
 * @param {Object} enumkeys 枚举keys
 */
function handle614ArrayVariables(variable, enumkeys = {}) {
  // eslint-disable-next-line prefer-const
  let [, arrayName, , arrayIndex] = variable.match(/([\w]+)(\[(\w+)\])/);
  let arrayFieldPath = '';
  function buildArrayPath(str, enumkeys) {
    let [index] = str.match(/[\w]+/);
    if (Number.isNaN(Number(index, 10))) {
      if (!enumkeys[index]) {
        throw new Error(`Can't find the enum key:${index} in array[enum]`);
      }
      index = enumkeys[index];
    }
    arrayFieldPath += `.@value.${index}`;
  }
  const arrayStr = variable.match(/\[\w+\]/g);
  arrayStr.forEach((a) => {
    buildArrayPath(a, enumkeys);
  });
  return { arrayFieldPath, arrayName, arrayIndex };
}
/**
 *  处理614结构体变量
 * @param {string} variable 变量名
 * @param {Object} enumkeys 枚举keys
 * @returns
 */
function handle614StructVariables(variable, enumkeys = {}) {
  // 先处理结构体
  const structName = (variable).match(/[\w]+/)[0];
  const newKey = utility.replaceAll(variable, '.', ':@value:');
  let fieldPath = `${utility.replaceAll(newKey, ':', '.')}.@value`;
  // 处理结构体内的数组
  if (fieldPath.match(/\[\w+\]/g)) {
    const arrayFields = fieldPath.split('.');
    arrayFields.forEach((key) => {
      if (key.match(/\[\w+\]/)) {
        const { arrayFieldPath, arrayName } = handle614ArrayVariables(key, enumkeys);
        fieldPath = utility.replaceAll(fieldPath, key, `${arrayName}${arrayFieldPath}`);
      }
    });
  }
  return { fieldPath, structName };
}
/**
 * 解析614测试用例excel表格测试用例
 * @param {String} funcFilePath 函数表格路径
 * @returns
 */
async function parse614ExcelFile(funcFilePath, enumkeys) {
  const regIndex = /(.*)\[(\d+)\]/;
  const funcTestCases = {};
  const testCaseGroups = new Map();
  const workSheetsFromFile = nodeXlsx.parse(funcFilePath);
  const typesMap = new Map();
  const {
    name: functionName,
    data: testCaseDatas,
  } = workSheetsFromFile[0];
  let types = [];
  const stubsData = new Set();
  workSheetsFromFile.forEach((sheet) => {
    if (sheet.name === '变量信息映射表' && sheet.data.length > 0) {
      sheet.data.splice(0, 1);
      types = sheet.data;
    }
  });
  if (types.length === 0 || !functionName || testCaseDatas.length === 0) {
    logger.debug(`614 parse ${funcFilePath} error `);
    return {};
  }
  for (let i = 0, len = types.length; i < len; i += 1) {
    const columnData = types[i];
    const columnName = columnData[0] || '';
    const columnType = columnData[1] || '';
    const columnValue = columnData[2] || '';
    if (columnName !== 'LoopCnt') {
      typesMap.set(columnName, {
        type: columnType,
        name: columnValue,
      });
    }
  }
  funcTestCases.functionName = functionName;
  funcTestCases.testCases = [];
  const tableHeader = testCaseDatas[0];
  const ioIndex = tableHeader.indexOf('I/O');
  const endIndex = tableHeader.indexOf('END');
  const desStartIndex = endIndex + 1;
  const desEndIndex = tableHeader.length;
  if (!ioIndex || !endIndex) {
    throw new PhoenixError('invalid request', 'import file format error');
  }
  for (let i = 1, len = testCaseDatas.length; i < len; i += 1) {
    const rowData = testCaseDatas[i];
    // 去掉CaseID为空的测试用例
    const CaseID = utility.isNumberType(rowData[0]) ? `${rowData[0]}` : rowData[0];
    if (CaseID) {
      const testCase = {
        returnVal: {},
        userDefinedFields: [],
      };
      // input
      for (let j = 0; j < ioIndex; j += 1) {
        const variableName = tableHeader[j];
        if (variableName) {
          testCase[variableName] = {
            value: utility.isNumberType(rowData[j]) ? `${rowData[j]}` : rowData[j],
            property: typesMap.get(variableName) || '',
          };
        }
      }
      // output
      for (let j = ioIndex + 1; j < endIndex; j += 1) {
        const variableName = tableHeader[j];
        testCase.returnVal[variableName] = {
          value: utility.isNumberType(rowData[j]) ? `${rowData[j]}` : rowData[j],
          property: typesMap.get(variableName) || '',
        };
      }
      // userDefinedFields
      for (let j = desStartIndex; j < desEndIndex; j += 1) {
        const variableName = tableHeader[j] || '';
        if (variableName === '所属编组' && rowData[j]) {
          const groupNumber = Number(rowData[j]);
          if (!testCaseGroups.has(groupNumber)) {
            testCaseGroups.set(groupNumber, []);
          }
          testCaseGroups.get(groupNumber).push(CaseID);
          testCase.groupNumber = groupNumber;
        } else {
          let value = '';
          if (rowData[j] && (rowData[j] === '×' || rowData[j] === '0')) {
            value = '0';
          }
          if (rowData[j] && (rowData[j] === '√' || rowData[j] === '1')) {
            value = '1';
          }
          if (variableName === '用例说明') {
            value = rowData[j] || '';
          }
          testCase.userDefinedFields.push({
            name: variableName,
            value,
          });
        }
      }
      // 将ReqID加到自定义字段
      testCase.userDefinedFields.push({
        name: 'ReqID',
        value: testCase.ReqID.value || '',
      });
      funcTestCases.testCases.push(testCase);
    }
  }
  const formatTestCases = [];
  // 构建新的测试用例数据
  funcTestCases.testCases.forEach((item) => {
    const testCase = {
      caseId: item.CaseID.value,
      groupNumber: item.groupNumber,
      execCount: item.LoopCnt.value || 1,
      input: {
        stubs: {},
        global: {},
        params: {},
      },
      output: {
        global: {},
      },
      returnVal: [],
      userDefinedFields: item.userDefinedFields,
    };
    try {
      Object.keys(item).forEach((key) => {
        const propertyName = item[key].property ? item[key].property.name : '';
        // 忽略固定字段
        if (!propertyName && !['returnVal', 'CaseID', 'ReqID', 'LoopCnt'].includes(key)) {
          return;
        }
        let type = 'none';
        if (propertyName === '一般形参') {
          type = 'params';
        }
        if (propertyName === '全局变量') {
          type = 'global';
        }
        // 处理检查项和函数返回值
        if (key === 'returnVal') {
          Object.keys(item.returnVal).forEach((retKey) => {
            //
            if (retKey.indexOf('[R]') !== -1) {
              // 结构体类型函数返回值
              let retFieldPath;
              const retFunctionName = retKey.match(/(\[R\])([\w]+)/)[2];
              if (retFunctionName !== functionName) {
                return;
              }
              const newKey = (retKey.split('[R]'))[1];
              if (retKey.indexOf('.') !== -1) { // 函数返回值结构体类型处理
                const { structName, fieldPath } = handle614StructVariables(newKey, enumkeys);
                const newfieldPath = fieldPath.replace(structName, '');
                key = structName;
                retFieldPath = `output.%${newfieldPath}`;
              } else {
                retFieldPath = 'output.%.@value';
              }
              testCase.returnVal.push({
                fieldPath: retFieldPath,
                value: item.returnVal[retKey].value,
                retFunctionName,
              });
            } else if (retKey.indexOf('.') !== -1) { // 检查项全局变量结构体类型处理
              const { structName, fieldPath } = handle614StructVariables(retKey, enumkeys);
              testCase.output.global[structName] = testCase.output.global[structName] || [];
              testCase.output.global[structName].push({
                fieldPath: `output.global.${fieldPath}`,
                value: item.returnVal[retKey].value,
              });
            } else if (retKey.match(/(.*)\[(\w+)\]/)) { // 检查项全局变量数组类型处理
              const { arrayFieldPath, arrayName } = handle614ArrayVariables(retKey, enumkeys);
              testCase.output.global[arrayName] = testCase.output.global[arrayName] || [];
              testCase.output.global[arrayName].push({
                fieldPath: `output.global.${arrayName}${arrayFieldPath}.@value`,
                value: item.returnVal[retKey].value,
              });
            } else { // 检查项全局变量基本类型返回值
              testCase.output.global[retKey] = testCase.output.global[retKey] || [];
              testCase.output.global[retKey].push({
                fieldPath: `output.global.${retKey}.@value`,
                value: item.returnVal[retKey].value,
              });
            }
          });
        } else if (key.indexOf('[I]') !== -1) { // 处理输入桩函数
          const newkey = key.replace('[I]', '');
          // 桩函数执行次数
          const indexMatch = newkey.match(regIndex);
          let stubName = newkey;
          if (item[key].value) {
            stubsData.add(stubName);
          }
          let index = 0;
          if (indexMatch) {
            [, stubName, index] = indexMatch;
            index = parseInt(index, 10) - 1;
          }
          if (!testCase.input.stubs[stubName]) {
            testCase.input.stubs[stubName] = [];
          }
          // stub stub[1] may both exists in columns
          let firstInvokeExists = false;
          // check if the returned value of first invoke already stored
          if (index === 0) {
            testCase.input.stubs[stubName].forEach((existItem) => {
              if (existItem.fieldPath !== `stubs.${stubName}.@value.0.%.@value`) {
                return;
              }
              // first invoke already stored
              firstInvokeExists = true;
              // if the new value is not empty, replace the exists one
              if (item[key].value) {
                existItem.value = item[key].value;
              }
            });
          }
          if (!firstInvokeExists) {
            testCase.input.stubs[stubName].push({
              fieldPath: `stubs.${stubName}.@value.${index}.%.@value`,
              value: item[key].value,
            });
          }
          // 桩函数执行次数
          if (index > 0) {
            const stubTimesFieldPath = `stubs.${stubName}.times`;
            let exist = false;
            const stubTimes = parseInt(index, 10);
            testCase.input.stubs[stubName].forEach((item) => {
              if (item.fieldPath === stubTimesFieldPath && stubTimes > item.value) {
                item.value = stubTimes;
                exist = true;
              }
            });
            if (!exist) {
              testCase.input.stubs[stubName].push({
                fieldPath: stubTimesFieldPath,
                stubTimes: true,
                value: stubTimes,
              });
            }
          }
        } else if (key.indexOf('.') !== -1) { // 处理输入结构体变量
          const { structName, fieldPath } = handle614StructVariables(key, enumkeys);
          testCase.input[type][structName] = testCase.input[type][structName] || [];
          testCase.input[type][structName].push({
            fieldPath: `variables.${type}.${fieldPath}`,
            value: item[key].value,
          });
        } else if (key.match(/(.*)\[(\w+)\]/)) { // 处理输入数组变量
          const { arrayName, arrayFieldPath } = handle614ArrayVariables(key, enumkeys);
          testCase.input[type][arrayName] = testCase.input[type][arrayName] || [];
          testCase.input[type][arrayName].push({
            fieldPath: `variables.${type}.${arrayName}${arrayFieldPath}.@value`,
            value: item[key].value,
          });
        } else if (type !== 'none') { // 一般形参输入处理
          testCase.input[type][key] = testCase.input[type][key] || [];
          testCase.input[type][key].push({
            fieldPath: `variables.${type}.${key}.@value`,
            value: item[key].value,
          });
        }
      });
    } catch (err) {
      throw new PhoenixError('invalid request', `import file format error:${err}`);
    }
    formatTestCases.push(testCase);
  });
  funcTestCases.testCases = formatTestCases;
  funcTestCases.testCaseGroups = testCaseGroups;
  funcTestCases.stubsData = Array.from(stubsData);
  return funcTestCases;
}
function getUnqualifiedType(allTypes, typeName) {
  if (typeName === 'void') {
    return typeName;
  }
  const type = allTypes.get(typeName);
  if (!type) {
    throw new Error(`Can't find the type(${typeName}) in typeSystem`);
  }
  const canonicalType = allTypes.get(type['@canonical']);
  if (!canonicalType) {
    throw new Error(`Can't find the canonical(${type['@canonical']}) in typeSystem`);
  }
  const unqualifiedType = allTypes.get(canonicalType['@unqualified']);
  if (!unqualifiedType) {
    throw new Error(`Can't find the unqualified(${canonicalType['@unqualified']}) in typeSystem`);
  }
  return unqualifiedType;
}
// 枚举类型值替换
function handleEnumTypeValue(fileTypeSystem, variable) {
  if (fileTypeSystem && fileTypeSystem['@attributes'] && fileTypeSystem['@attributes'].includes('isEnumeral')) {
    const enums = fileTypeSystem['@enums'] || {};
    Object.keys(enums).forEach((key) => {
      if (enums[key] === variable.value) {
        variable.value = key;
      }
    });
  }
}
/**
 * 处理614测试用例导入的编组信息
 * @param {Object} groups 编组信息数据
 * @param {String} functionId 所属函数id
 * @param {Array[String]} topTestCaseIds 顶层测试用例id
 */
async function handle614TestCaseGroup(functionId, groups, topTestCaseIds) {
  const { projectId, versionId, fileId } = await Func.findById(functionId);
  const originalGroups = await TestCaseGroupModel.getGroupByFunctionId(functionId);
  const groupNumberToIdMapping = new Map();
  const groupNumberToChildrenMapping = new Map();
  const insertGroups = new Map();
  const updateGroups = new Map();
  const groupChildrenIdToNumberMapping = new Map();
  const testCaseGroupNumberChangeMapping = new Map();
  let currentGroupNumber = 0;
  let insertGroupTestCaseIds = [];
  const testCaseToTopGroupNumbers = new Set();
  originalGroups.forEach((g) => {
    const newChildrens = (g.children.map((v) => {
      const id = v.id.toString();
      groupChildrenIdToNumberMapping.set(id, { original: g.number });
      return id;
    })).filter((id) => {
      // 过滤掉已经移动到顶端的用例
      if (topTestCaseIds.includes(id)) {
        testCaseToTopGroupNumbers.add(g.number);
        return false;
      }
      return true;
    });
    groupNumberToChildrenMapping.set(g.number, newChildrens);
    if (newChildrens.length === 0) {
      updateGroups.set(g.number, []);
    }
    groupNumberToIdMapping.set(g.number, g._id.toString());
    currentGroupNumber = g.number > currentGroupNumber ? g.number : currentGroupNumber;
  });
  // 处理表格中用例全部移动到顶层的情况
  if (groups.size === 0 && testCaseToTopGroupNumbers.size !== 0) {
    testCaseToTopGroupNumbers.forEach((gid) => {
      updateGroups.set(gid, groupNumberToChildrenMapping.get(gid));
    });
  } else {
    // 筛选已存在的用例编组变更
    groups.forEach((ids, key) => {
      ids.forEach((id) => {
        if (groupChildrenIdToNumberMapping.has(id) && groupChildrenIdToNumberMapping.get(id).original !== key) {
          testCaseGroupNumberChangeMapping.set(id, {
            ...groupChildrenIdToNumberMapping.get(id),
            new: key,
          });
        }
      });
    });
    // 处理函数表格传进来的新的编组用例
    groups.forEach((ids, groupNumber) => {
      // 新编组
      if (!groupNumberToChildrenMapping.has(groupNumber)) {
        insertGroups.set(groupNumber, ids);
        insertGroupTestCaseIds = [...insertGroupTestCaseIds, ...ids];
        return;
      }
      // 已有编组内用例顺序按导入的最新顺序排序，原编组独有的用例在新顺序中排在最后
      const ogroupTestCaseIds = groupNumberToChildrenMapping.get(groupNumber);
      updateGroups.set(groupNumber, ids);
      ogroupTestCaseIds.forEach((id) => {
        if (!ids.includes(id) && !testCaseGroupNumberChangeMapping.has(id)) {
          updateGroups.get(groupNumber).push(id);
        }
      });
    });
  }
  function formatGroupTestCaseId(ids) {
    return ids.map((id) => ({
      id: mongoose.Types.ObjectId(id),
      childType: TestCaseGroupModel.type.testCase,
    }));
  }
  // 更新编组
  if (updateGroups.size > 0) {
    await utility.arrayChunkOperation(Array.from(updateGroups), 5, ([groupNumber, ids]) => TestCaseGroupModel
      .editTestCaseGroup({ children: formatGroupTestCaseId(ids.filter((id) => !insertGroupTestCaseIds.includes(id))) },
        groupNumberToIdMapping.get(groupNumber)));
  }
  // 删除原编组中已经移除出去的用例
  if (testCaseGroupNumberChangeMapping.size > 0) {
    const newRemoveIds = new Map();
    const groupIds = [];
    testCaseGroupNumberChangeMapping.forEach((value, id) => {
      // 只更新不在表格中的编组和原有测试用例编组没有变化的也不更新
      if (groups.has(value.original)) {
        return;
      }
      const groupId = groupNumberToIdMapping.get(value.original);
      if (!newRemoveIds.has(groupId)) {
        groupIds.push(groupId);
        newRemoveIds.set(groupId, []);
      }
      newRemoveIds.get(groupId).push(id);
    });
    if (groupIds.length > 0) {
      const updateOriginalGroups = await TestCaseGroupModel.getGroupById(groupIds);
      await utility.arrayChunkOperation(updateOriginalGroups, 5, async (group) => {
        const newIds = group.children.filter((item) => !(newRemoveIds.get(group._id.toString()).includes(item.id.toString())));
        await TestCaseGroupModel.editTestCaseGroup({ children: newIds }, group._id);
      });
    }
  }
  // 新加的编组
  if (insertGroups.size > 0) {
    const numberName = await numbers.getTestcaseGroupNumberName(functionId);
    // 原始函数没有编组，需要初始化编组number
    if (originalGroups.length === 0) {
      currentGroupNumber = (await numbers.next(numberName)).number;
    }
    await utility.arrayChunkOperation(Array.from(insertGroups), 1, async ([groupNumber, ids]) => {
      groupNumber = Number(groupNumber);
      ids = formatGroupTestCaseId(ids);
      if (groupNumber > currentGroupNumber) {
        await numbers.restore(numberName, groupNumber);
      }
      const doc = {
        projectId,
        versionId,
        fileId,
        functionId: mongoose.Types.ObjectId(functionId),
        number: groupNumber,
        children: ids,
      };
      const group = await (new TestCaseGroup(doc)).save();
      const groupId = group._id;
      await TestCaseGroup.updateOne(
        { _id: groupId }, { $set: { path: [groupId] } }
      );
    });
  }
  // 清除空编组
  await TestCaseGroup.deleteMany({ functionId, children: { $size: 0 } });
}
/**
 * 处理614单文件用例数据
 * @param {Object} fileData excel文件解析之后的数据
 * @param {Object} allFuncTestCases 所有函数对应的格式化用例
 * @param {Map} allVariableMap 所有的variable数据
 * @param {Map} allFileTypeMap 带文件的类型数据
 * @param {Object} version 版本
 * @param {Object} locale
 * @param {Object} userDefinedFields 自定义字段
 * @param {object} allTypes 不带文件的类型
 * @returns
 */
async function handle614SingleFile(
  fileData,
  allFuncTestCases,
  allVariableMap,
  allFileTypeMap,
  version,
  locale,
  userDefinedFields,
  allTypes
) {
  const promises = [];
  const {
    testCases,
    testCaseGroups,
    functionName,
    functionId,
    fileId,
  } = fileData;
  const topTestCaseIds = [];
  const userDefinedCaseIdField = userDefinedFields.find((field) => field.name === 'CaseID');
  testCases.forEach((inputTestCase) => {
    promises.push((async () => {
      let testCase;
      const {
        caseId,
        userDefinedFields: userDefinedFieldsValues,
        execCount,
        groupNumber,
      } = inputTestCase;
      const doc = {
        execCount,
        userDefinedFields: [{
          value: caseId,
          key: userDefinedCaseIdField._id,
          name: userDefinedCaseIdField.name,
        }],
      };
      // 用例caseId不存在
      let originalTestCase = allFuncTestCases.get(functionName).testCases.get(caseId);
      if (!originalTestCase) {
        testCase = await addTest(
          version.projectId,
          version._id,
          fileId,
          functionId,
          {
            data: {},
            ...doc,
            versionType: version.versionType,
            description: '',
          },
        );
        originalTestCase = getJson(testCase, locale);
        originalTestCase.functionName = functionName;
        originalTestCase.functionId = testCase.functionId;
        originalTestCase.fileId = testCase.fileId;
        originalTestCase._id = testCase._id;
      }
      testCase = await format614TestCase(
        allVariableMap,
        allFileTypeMap,
        originalTestCase,
        version.versionType
      );
      // 编组内测试用例caseId替换
      if (groupNumber) {
        const index = testCaseGroups.get(groupNumber).findIndex((v) => v === caseId);
        if (index !== -1) {
          testCaseGroups.get(groupNumber)[index] = testCase.testCaseId.toString();
        }
      } else {
        topTestCaseIds.push(testCase.testCaseId.toString());
      }
      // 将表格中编辑的自定义字段数据过滤出来
      if (userDefinedFields.length > 0 && userDefinedFieldsValues.length > 0) {
        userDefinedFieldsValues.forEach((item) => {
          const definedField = userDefinedFields.find((field) => field.name === item.name);
          if (definedField) {
            doc.userDefinedFields.push({ ...item, key: definedField._id });
          }
          if (item.name === '用例说明') {
            doc.description = item.value;
          }
        });
      }
      // 将测试用例中原有的自定义字段数据回填
      testCase.userDefinedFields.forEach((item) => {
        const testCaseUserDefinedField = userDefinedFieldsValues.find((field) => item.name === field.name);
        if (!testCaseUserDefinedField) {
          doc.userDefinedFields.push(item);
        }
      });
      // 更新测试用例执行次数和自定义字段信息
      await updateTest(testCase.testCaseId, doc);
      const values = [];
      // 处理stubs输入
      if (testCase.input.stubs) {
        const { stubs } = testCase.input;
        const inputStubs = inputTestCase.input.stubs;
        stubs.forEach((stub) => {
          const stubName = stub.originalName || stub.stubName;
          if (inputStubs[stubName]) {
            inputStubs[stubName].forEach((item) => {
              // 桩函数有可能拼接文件id
              item.fieldPath = item.fieldPath.replace(`.${stubName}.`, `.${stub.stubName}.`);
              let exist = true;
              if (item.stubTimes && stub.times && stub.times.length > item.value) {
                exist = false;
              }
              if (exist) {
                handleEnumTypeValue(getUnqualifiedType(allTypes, stub.typeName), item);
                values.push(item);
              }
            });
          }
        });
      }
      // 处理params和global输入
      ['params', 'global'].forEach((key) => {
        if (testCase.input[key]) {
          const testCaseVariables = testCase.input[key];
          const inputValues = inputTestCase.input[key];
          testCaseVariables.forEach((variable) => {
            const originalName = variable.originalName ? variable.originalName : variable.name;
            if (inputValues[originalName]) {
              inputValues[originalName].forEach((item) => {
                // 集成测试新的变量名有可能拼接文件id
                item.fieldPath = item.fieldPath.replace(`.${originalName}.`, `.${variable.name}.`);
                handleEnumTypeValue(getUnqualifiedType(allTypes, variable.typeName), item);
                values.push(item);
              });
            }
          });
        }
      });
      // 处理global输出
      if (testCase.output.global) {
        const variables = testCase.output.global;
        const inputGlobal = inputTestCase.output.global;
        variables.forEach((variable) => {
          const originalName = variable.originalName ? variable.originalName : variable.name;
          if (inputGlobal[originalName]) {
            inputGlobal[originalName].forEach((item) => {
              // 集成测试新的变量名有可能拼接文件id
              item.fieldPath = item.fieldPath.replace(`.${originalName}.`, `.${variable.name}.`);
              handleEnumTypeValue(getUnqualifiedType(allTypes, variable.typeName), item);
              values.push(item);
            });
          }
        });
      }
      // 处理函数返回值
      if (testCase.returnValueData && inputTestCase.returnVal.length > 0) {
        inputTestCase.returnVal.forEach((inputReturn) => {
          if (testCase.returnValueData[inputReturn.fieldPath]) {
            handleEnumTypeValue(getUnqualifiedType(allTypes, testCase.returnValueData[inputReturn.fieldPath].typeName), inputReturn);
            values.push(inputReturn);
          }
        });
      }
      // 更新测试用例值
      await Promise.all(values.map((item) => updateTest(
        testCase.testCaseId, { key: item.fieldPath, value: item.value }
      )));
    })());
  });
  await Promise.all(promises);
  await handle614TestCaseGroup(functionId, testCaseGroups, topTestCaseIds);
  return { functionId };
}
async function import614TestCases(testsPath, version, locale, zip = true, fileExtName) {
  const { versionType, _id: versionId } = version;
  // 614用户自定义字段
  const udfName = 'CaseID';
  const userDefinedFields = await UserDefinedFields.find({
    domain: version.versionType,
    scope: 'testcase',
  });
  const userDefinedCaseIdField = userDefinedFields.find((field) => field.name === udfName);
  if (!userDefinedCaseIdField) {
    return;
  }
  const importExcelFilePaths = [];
  const importFunctionNames = [];
  const ignoreFileNames = ['__MACOSX'];
  const parseFileExtNames = ['.xlsx', '.xlsm'];
  if (zip) {
    const testsDir = `${testsPath}.dest`;
    await fs.remove(testsDir);
    await utility.uncompress(testsPath, testsDir);
    const handleDir = function handleDir(testsDir) {
      const files = fs.readdirSync(testsDir);
      files.forEach((fileName) => {
        if (ignoreFileNames.includes(fileName)) {
          return;
        }
        const childFilePath = path.join(testsDir, fileName);
        const extname = path.extname(childFilePath);
        const fileStat = fs.statSync(childFilePath);
        if (fileStat.isFile() && extname && parseFileExtNames.includes(extname)) {
          const parseChildFile = nodeXlsx.parse(childFilePath);
          if (parseChildFile.length === 0) {
            return;
          }
          importFunctionNames.push(parseChildFile[0].name);
          importExcelFilePaths.push(childFilePath);
        } else if (fileStat.isDirectory()) {
          handleDir(childFilePath);
        }
      });
    };
    handleDir(testsDir);
  } else {
    if (!parseFileExtNames.includes(fileExtName)) {
      return;
    }
    const parseFile = nodeXlsx.parse(testsPath);
    if (parseFile.length > 0) {
      importFunctionNames.push(parseFile[0].name);
      importExcelFilePaths.push(testsPath);
    }
  }
  const allFileMap = await fileModel.getSourceFileByVersionId(versionId, null, { _id: 1, path: 1 });
  const allFuncMap = await functions.getFunctionByVersionId(versionId, {
    _id: 1, fileId: 1, functionName: 1, functionAttrs: 1, kind: 1,
  });
  const allFuncVariableStubs = await functions.getFunctionStubsByVersionId(versionId);
  const allVariableMap = new Map();
  const allFileTypeMap = new Map();
  const allTypes = new Map();
  const allFuncTestCases = new Map();
  const allImportFuncIds = [];
  const enumkeys = {};
  const allImportFunctions = await Func.find({ versionId, functionName: { $in: importFunctionNames } }, { functionName: 1, fileId: 1, _id: 1 });
  allImportFunctions.forEach((func) => {
    const { functionName, fileId, _id } = func;
    allImportFuncIds.push(_id);
    let newFunctionName = functionName;
    // 去掉C++函数对应的函数名输入参数和（）
    if (newFunctionName.indexOf('(') !== -1) {
      [newFunctionName] = functionName.split('(');
    }
    if (!allFuncTestCases.has(newFunctionName)) {
      allFuncTestCases.set(newFunctionName, {
        fileId,
        functionId: _id,
        testCases: new Map(),
      });
    }
  });
  (await FunctionVariable.find({ functionId: { $in: allImportFuncIds } })).forEach((variable) => {
    allVariableMap.set(variable.functionId.toString(), variable);
  });
  (await fileModel.getFileTypeSystemByVersion(versionId)).forEach((fileType) => {
    allFileTypeMap.set(fileType.fileId.toString(), fileType);
    Object.keys(fileType.types).forEach((typeName) => {
      const type = fileType.types[typeName];
      allTypes.set(typeName, type);
      if (type['@attributes']
        && type['@attributes'].includes('isEnumeral')
        && type['@enums']
      ) {
        const enums = type['@enums'];
        Object.keys(enums).forEach((enumkey) => {
          enumkeys[enums[enumkey]] = enumkey;
        });
      }
    });
  });

  const testCases = await TestCase.aggregate([
    {
      $match: { functionId: { $in: allImportFuncIds } },
    }, {
      $lookup: {
        from: 'functions',
        localField: 'functionId',
        foreignField: '_id',
        as: 'newTestCase',
      },
    },
    { $unwind: '$newTestCase' },
    {
      $project: {
        functionId: '$newTestCase._id',
        functionName: '$newTestCase.functionName',
        data: 1,
        userDefinedFields: 1,
        fileId: 1,
      },
    },
  ]);
  await utility.arrayChunkOperation(testCases, 100, async (testCase) => {
    let caseId;
    testCase.userDefinedFields.forEach((udf) => {
      if (udf.name === udfName) {
        caseId = udf.value;
      }
    });
    if (caseId) {
      allFuncTestCases.get(testCase.functionName).testCases.set(caseId, testCase);
    }
  });
  await utility.arrayChunkOperation(importExcelFilePaths, 10, async (filePath) => {
    const exist = await fs.pathExists(filePath);
    if (!exist) {
      return Promise.resolve();
    }
    const fileData = await parse614ExcelFile(filePath, enumkeys);
    // 处理测试用例导入赋值
    if (!fileData.functionName || !allFuncTestCases.has(fileData.functionName)) {
      return Promise.resolve({});
    }
    const { fileId, functionId } = allFuncTestCases.get(fileData.functionName);
    if (!functionId) {
      return Promise.resolve({});
    }
    fileData.functionId = functionId;
    fileData.fileId = fileId;
    // 集成测试更新桩函数打桩状态
    if (versionType === projectVersions.versionType.integrationTest) {
      await invokeRelation.bulkUpdateRelations([{
        updateMany: {
          filter: {
            functionId: mongoose.Types.ObjectId(functionId),
            toFuncName: { $in: fileData.stubsData },
          },
          update: {
            enableStub: true,
          },
        },
      }]);
      const variable = await functions.getRelationFuncsVariables(functionId,
        {
          spliceFileId: true, allFileMap, allFuncMap, allFuncVariableStubs,
        });
      allVariableMap.set(functionId.toString(), variable);
    }
    await handle614SingleFile(
      fileData,
      allFuncTestCases,
      allVariableMap,
      allFileTypeMap,
      version,
      locale,
      userDefinedFields,
      allTypes
    );
    await functions.updateFunctionCoverage(functionId);
    if (versionType === projectVersions.versionType.integrationTest) {
      await functions.updateFunctionInvokeCoverage(functionId);
    }
    return Promise.resolve();
  });
}
/**
 * 批量导入测试用例
 * @param {String} versionId 项目版本id
 * @param {String} testsPath 测试用例路径
 * @param {Object} version 项目版本
 * @param {Object} project 项目
 * @param {Object} creator 创建者
 * @param {Boolean} zip 传入压缩文件
 * @param {String} sourcePath 压缩包文件/文件夹路径
 * @param {String} targetPath 导入的目标文件/文件夹路径
 * @param {boolean} isUnitTestToIntegrationTest 是否是移植单测
 * @param {boolean} isRegressionReusedCases 是否回归测试复用用例
 * @param {ProgressIndicator} progressIndicator 进度指示器
 * @return {Promise}
 */
async function importTests(testsPath, version, project, creator, zip = true, sourcePath, targetPath, isUnitTestToIntegrationTest = false,
  isRegressionReusedCases = false, progressIndicator = null) {
  const { _id: versionId, versionType } = version;
  let exportFormat;
  let testsDir = `${testsPath}.dest`;
  let versionConfigInfo;
  if (sourcePath && targetPath) {
    exportFormat = 'json';
  }
  const commonPro = await common.complementByProjectVersionId(versionId, version, project, creator);
  version = commonPro.version;
  project = commonPro.project;
  creator = commonPro.creator;
  if (zip) {
    await fs.remove(testsDir);
    await utility.uncompress(testsPath, testsDir);
  } else {
    testsDir = testsPath;
  }
  const [projectName] = utility.getDirChildren(testsDir);
  const [versionName] = utility.getDirChildren(path.join(testsDir, projectName));
  if (!projectName || !versionName) {
    throw new PhoenixError('invalid request', 'import file path error ');
  }
  const versionConfigPath = path.join(testsDir, `${projectName}/versionInfo.json`);
  let exportFuncFile;
  if (await fs.pathExists(versionConfigPath)) {
    versionConfigInfo = await fs.readJson(versionConfigPath);
    // 集成测试无法导入单元测试
    if (versionConfigInfo.versionType
      && versionConfigInfo.versionType === projectVersions.versionType.integrationTest
      && version.versionType === projectVersions.versionType.unitTest) {
      throw new PhoenixError('invalid request', 'import file version type error ');
    }
    exportFormat = versionConfigInfo.exportFormat;
    exportFuncFile = !!versionConfigInfo.exportFuncFile;
    if (!['json', 'xlsx'].includes(exportFormat)) {
      throw new PhoenixError('invalid request', 'import file format error ');
    }
  }
  const allFilePathOfVersion = new Map();
  const allFuncsOfFileAndMangledId = new Map();
  const allFuncsVariablesMap = new Map();
  const allFuncMap = new Map();
  let fileExts = [];
  // 获取源码文件
  Object.keys(version.languages).forEach((key) => {
    if (version.languages[key].suffixes) {
      fileExts.push(...version.languages[key].suffixes);
    }
  });
  fileExts = fileExts.concat(version.headers.suffixes);
  const [resFiles, resFunctions, resFileTypeSystems, allFuncsVariables] = await Promise.all([
    File.find({
      versionId, kind: { $in: [fileModel.kind.file, fileModel.kind.fileOutsideProject] }, extname: { $in: fileExts },
    }, ['_id', 'path', 'fileName', 'versionId', 'projectId']).lean(),
    Func.find({ versionId, kind: { $in: fileElement.getCountableKinds() } },
      ['_id', 'fileId', 'functionName', 'versionId', 'mangledId', 'kind', 'projectId', 'functionAttrs', 'creatorId']).lean(),
    fileModel.getFileTypeSystemByVersion(versionId),
    FunctionVariable.find({ versionId }).lean(),
  ]);
  logger.verbose('handle file and function');
  // 处理所有avriables
  allFuncsVariables.forEach((variable) => {
    const vfunctionId = variable.functionId.toString();
    if (!allFuncsVariablesMap.has(vfunctionId)) {
      allFuncsVariablesMap.set(vfunctionId, variable);
    }
  });
  // 文件处理
  const allFileMap = new Map();
  resFiles.forEach((file) => {
    allFileMap.set(file._id.toString(), file);
    if (!allFilePathOfVersion.has(file.path)) {
      allFilePathOfVersion.set(file.path, file);
    }
  });
  // 文件类型系统
  resFileTypeSystems.forEach((typeSystem) => {
    const file = allFileMap.get(typeSystem.fileId.toString());
    if (file) {
      file.typeSystem = typeSystem;
    }
  });
  // 获取所有函数map
  resFunctions.forEach((func) => {
    allFuncMap.set(func._id.toString(), func);
  });
  // 函数,variable组装
  await utility.arrayChunkOperation(resFunctions, 100, async (func) => {
    const key = `${func.fileId.toString()}-${func.mangledId}`;
    if (!allFuncsOfFileAndMangledId.has(key)) {
      allFuncsOfFileAndMangledId.set(key, func);
    }
    const file = allFileMap.get(func.fileId.toString());
    if (!file) {
      return;
    }
    const { _id: functionId, mangledId, functionName } = func;
    if (!file.functionMap) {
      file.functionMap = new Map();
    }
    if (!file.funcNameToMangleId) {
      file.funcNameToMangleId = new Map();
    }
    const variable = allFuncsVariablesMap.get(functionId.toString());
    func.variables = variable || {};
    file.functionMap.set(mangledId, func);
    file.funcNameToMangleId.set(functionName, mangledId);
  });
  // 自定义字段
  const udfs = await userDefinedFieldsModel.listUserDefinedField(version.versionType, 'function');
  const udfsMap = new Map();
  if (udfs.list) {
    udfs.list.forEach((udf) => {
      udfsMap.set(udf.name, udf._id);
    });
  }
  const importedTestsData = {};
  const ableImportFilePaths = [];
  const importedFunctions = {};
  await utility.arrayChunkOperation(resFiles, 50, async (file) => {
    if (!file.functionMap) {
      return;
    }
    if (file.functionMap.size === 0) {
      return;
    }
    let testCaseFilePath = file.path;
    if (sourcePath && targetPath) {
      // 只取匹配的路径
      if (testCaseFilePath.indexOf(targetPath) === -1) {
        return;
      }
      testCaseFilePath = path.join(testsDir, testCaseFilePath.replace(targetPath, sourcePath));
    } else {
      testCaseFilePath = path.join(testsDir, projectName, versionName, testCaseFilePath);
    }
    if (!exportFuncFile && exportFormat === 'json' && testCaseFilePath.substring(testCaseFilePath.lastIndexOf('.') + 1) !== 'json') {
      testCaseFilePath += `.${exportFormat}`;
    }
    const exist = await fs.pathExists(testCaseFilePath);
    if (exist) {
      ableImportFilePaths.push({ file, testCaseFilePath });
    }
  });
  const [testCaseHandleProgress, variableHandleProgress] = progressIndicator ? progressIndicator.split([0.9, 0.1]) : [null, null];
  logger.verbose('start import test case');
  const versionTestcaseCount = await TestCase.countDocuments({ versionId });
  await utility.arrayChunkOperation(ableImportFilePaths, 2, async (item) => {
    const res = await handleFileTestCaseImport(
      item.file,
      item.testCaseFilePath,
      exportFormat,
      version,
      allFilePathOfVersion,
      allFuncsOfFileAndMangledId,
      allFileMap,
      fileExts,
      udfsMap,
      importedTestsData,
      importedFunctions,
      isRegressionReusedCases,
      exportFuncFile,
      versionTestcaseCount
    );
    if (testCaseHandleProgress) {
      testCaseHandleProgress.advance(1 / ableImportFilePaths.length);
    }
    return res;
  });
  logger.verbose('start handle function variable');
  // 处理funcvariable
  // 兼容老格式的测试用例导入
  const functionVariables = importedFunctions.size === 0 ? {} : importedFunctions;
  if (versionType === projectVersions.versionType.integrationTest) {
    await utility.arrayChunkOperation(Object.keys(functionVariables), 50, async (functionId) => {
      await functions.integrationTestUpdateFunctionVariableByVariable(
        functionId,
        functionVariables[functionId],
        true,
        allFilePathOfVersion
      );
      // 将第一层打桩
      if (isUnitTestToIntegrationTest) {
        logger.verbose('开始第一层打桩');
        await functions.updateAllRelationStubStatusByFunctionId(functionId, versionId, true, 2);
        await functions.updateInvokeCoverageTotal([functionId], versionId);
        await functions.integrationUpdateTestCaseStubs(functionId);
      }
      if (variableHandleProgress) {
        variableHandleProgress.advance(1 / Object.keys(functionVariables).length);
      }
    });
  } else {
    await utility.arrayChunkOperation(Object.keys(functionVariables), 50, async (functionId) => {
      const res = await functions.updateFunctionVariableByVariable(
        functionId,
        functionVariables[functionId],
        true,
      );
      if (variableHandleProgress) {
        variableHandleProgress.advance(1 / Object.keys(functionVariables).length);
      }
      return res;
    });
  }
  // 纠正测试用例的标识
  if (versionTestcaseCount === 0) {
    const identifierNumbers = await TestCase.find({ versionId }, ['identifierNumber']);
    await numbers.restore(
      numbers.getTestcaseIdentifierNumberName(versionId),
      identifierNumbers.length > 0 ? Math.max(...identifierNumbers.map((item) => item.identifierNumber)) : 0
    );
  }
}
/**
 * 移除测试用例绝对地址没有值的基地址和表达式
 * @param {Object} testCase 测试用例
 */
function removeEmptyFixedAddressOfTestCase(testCase) {
  if (testCase.data && testCase.data.fixedAddrs) {
    const { exprs, bases } = testCase.data.fixedAddrs;
    if (testCase.data.output.fixedAddrs && testCase.data.output.fixedAddrs.bases) {
      delete testCase.data.output.fixedAddrs.bases;
    }
    if (exprs) {
      Object.keys(exprs).forEach((exprName) => {
        if (!exprs[exprName]['@value']) {
          delete exprs[exprName];
        }
      });
    }
    if (bases) {
      Object.keys(bases).forEach((baseName) => {
        if (bases[baseName].pointerTargets
          && bases[baseName].pointerTargets['0']
          && bases[baseName].pointerTargets['0']['@value']) {
          const baseValues = bases[baseName].pointerTargets['0']['@value'];
          let removeBase = true;
          Object.keys(baseValues).forEach((index) => {
            if (!baseValues[index]['@value']) {
              delete baseValues[index];
              return;
            }
            removeBase = false;
          });
          if (removeBase) {
            delete bases[baseName];
          }
        }
      });
    }
  }
  return testCase;
}

async function addStubVariadicParams(testId, variadicParams, stubInfo) {
  const testcase = await getTest(testId);
  const { time, params, fileId } = variadicParams;
  let { stubName } = variadicParams;
  if (stubInfo[`${stubName}@${fileId}`]) {
    stubName = `${stubName}@${fileId}`;
  }
  const stubsParams = {};
  const outputStubs = {};
  let originalStubParams = {};
  if (testcase.data.stubs[stubName]['@value']
    && testcase.data.stubs[stubName]['@value'][time]
    && testcase.data.stubs[stubName]['@value'][time].params
  ) {
    originalStubParams = testcase.data.stubs[stubName]['@value'][time].params;
  }
  Object.keys(originalStubParams).forEach((index) => {
    if (!originalStubParams[index].isVariadic) {
      stubsParams[index] = originalStubParams[index];
    }
  });
  let index = stubInfo[stubName].params.length;
  params.forEach((param) => {
    const obj = {
      '@type': param.type,
      '@name': param.name,
      isVariadic: true,
    };
    if (originalStubParams[index]) {
      if (originalStubParams[index]['@type'] === param.type && originalStubParams[index]['@value']) {
        obj['@value'] = originalStubParams[index]['@value'];
      } else if (originalStubParams[index]['@type'] !== param.type) {
        outputStubs[`data.output.stubs.${stubName}.@value.${time}.params.${index}`] = 1;
      }
    }
    stubsParams[index] = obj;
    index += 1;
  });
  Object.keys(originalStubParams).forEach((index) => {
    if (index > Object.keys(stubsParams).length - 1) {
      outputStubs[`data.output.stubs.${stubName}.@value.${time}.params.${index}`] = 1;
    }
  });
  await TestCase.updateOne({ _id: mongoose.Types.ObjectId(testId) }, { $set: { [`data.stubs.${stubName}.@value.${time}.params`]: stubsParams }, $unset: outputStubs });
  if (!utility.isEmpty(outputStubs)) {
    await removeResultOfExecution(testId);
  }
}
/**
 *  更新测试用例注释信息
 * @param {String:ObjectId} testcaseId 测试用例id
 * @param {Object} testcaseId 测试用例id
 * @param {Array[Object]} userDefinedField 测试用例自定义字段
 */
async function updateTestcaseComment(testcaseId, userDefinedField) {
  if (!userDefinedField) {
    return;
  }
  const testcase = await TestCase.findOne({ _id: testcaseId }, ['userDefinedFields', 'functionId', 'fileId']);
  if (!testcase) {
    return;
  }
  const testCaseUserDefinedFields = testcase.userDefinedFields ? testcase.userDefinedFields : [];
  const testcaseCommentData = {};
  const commentDatas = await FileComment.find({
    fileId: testcase.fileId, type: { $in: [fileModel.fileCommentType.lineEnd, fileModel.fileCommentType.lineAbove] },
  }, ['line', 'comment']);
  const functionCfg = await functions.getFunctionCFG(testcase.functionId);
  const nodes = Object.values(functionCfg.nodes) || [];
  function handleCfgCommentLine(nodes) {
    nodes.forEach((node) => {
      const testIds = node.testIds ? Object.keys(node.testIds) : [];
      if (testIds.length === 0 || !testIds.includes(testcaseId.toString())) {
        return;
      }
      if (node.statements && Object.keys(node.statements).length > 0) {
        handleCfgCommentLine(Object.values(node.statements));
      } else {
        const aboveLine = node.begin.line > 0 ? node.begin.line - 1 : node.begin.line;
        const currentLine = node.end.line;
        commentDatas.forEach((commentData) => {
          if (commentData.line === currentLine || commentData.line === aboveLine) {
            const str = `${commentData.line}:${commentData.comment}`;
            testcaseCommentData[commentData.line] = str;
          }
        });
      }
    });
  }
  handleCfgCommentLine(nodes);
  let commentStr = '';
  if (Object.keys(testcaseCommentData).length > 0) {
    commentStr = Object.keys(testcaseCommentData).reduce((preStr, key) => (preStr ? `${preStr}\n${testcaseCommentData[key]}` : testcaseCommentData[key]), commentStr);
  }
  let exist = false;
  testCaseUserDefinedFields.forEach((item) => {
    if (item.key.toString() === userDefinedField._id.toString()) {
      item.value = commentStr;
      exist = true;
    }
  });
  if (!exist) {
    testCaseUserDefinedFields.push({ name: userDefinedField.name, key: userDefinedField._id, value: commentStr });
  }
  await TestCase.updateOne({ _id: testcaseId }, { $set: { userDefinedFields: testCaseUserDefinedFields } });
}
/**
 *  获取被测函数参与集成函数的打桩信息和所有参与集成函数的文件
 * @param {Array[Object]} funcs 入口函数
 * @param {Object} version 版本信息
 */
async function integrationTestGenerateTestStubsAndFiles(funcs, version) {
  const filesOfVersionMap = await fileModel.getSourceFileByVersionId(version._id, null, { _id: 1, path: 1, fileName: 1 });
  const invokeFuncsVariableGlobalsMap = new Map();
  (await FunctionVariable.find(
    { versionId: version._id },
    ['functionId', 'variables.global', 'externalGlobals', 'fileId']
  ).lean()).forEach((variable) => {
    invokeFuncsVariableGlobalsMap.set(variable.functionId.toString(), variable);
  });
  const globalsDefinedOfVersionMap = new Map();
  const globalsOfVersion = await FileGlobalVariable.find({ versionId: version._id, globals: { $exists: true } }, {
    globals: 1,
    fileId: 1,
  }).lean();
  globalsOfVersion.forEach(({ fileId, globals }) => {
    Object.keys(globals).forEach((globalName) => {
      if (!globalsDefinedOfVersionMap.has(globalName)) {
        globalsDefinedOfVersionMap.set(globalName, { defined: [], other: [] });
      }
      if (filesOfVersionMap.has(fileId.toString())) {
        const file = filesOfVersionMap.get(fileId.toString());
        const key = globals[globalName]['@attributes'].includes('isDefined') ? 'defined' : 'other';
        globalsDefinedOfVersionMap.get(globalName)[key].push({
          fileId: file._id.toString(),
          fileName: file.fileName,
          path: file.path,
        });
      }
    });
  });
  const funcsOfVersionMap = new Map();
  (await Func.find({ versionId: version._id, kind: { $in: fileElement.getCountableKinds() } },
    ['_id', 'functionName', 'language', 'functionAttrs']).lean()).forEach((func) => {
    funcsOfVersionMap.set(func._id.toString(), func);
  });
  return utility.arrayChunkOperation(funcs, 20, async (func) => {
    const functionId = func._id.toString();
    const invokeFuncs = await invokeRelation.retrieveRelations(functionId, { initRelations: true });
    const invokeFuncsVariablesMap = new Map();
    const relatedFilesSet = new Set();
    relatedFilesSet.add(filesOfVersionMap.get(func.fileId.toString()).path);
    func.stubs = {};
    func.files = [];
    invokeFuncs.forEach((invokeFunc) => {
      const {
        toFuncId,
        enableStub,
        toFuncFileId,
        toFuncName,
        toFuncMangledId,
        fromFuncId,
      } = invokeFunc;
      if (toFuncFileId) {
        relatedFilesSet.add(filesOfVersionMap.get(toFuncFileId.toString()).path);
      }
      if (toFuncId && invokeFuncsVariableGlobalsMap.has(toFuncId.toString()) && toFuncId.toString() !== functionId) {
        invokeFuncsVariablesMap.set(toFuncId.toString(), invokeFuncsVariableGlobalsMap.get(toFuncId.toString()));
      }
      if (fromFuncId && invokeFuncsVariableGlobalsMap.has(fromFuncId.toString() && fromFuncId.toString() !== functionId)) {
        invokeFuncsVariablesMap.set(fromFuncId.toString(), invokeFuncsVariableGlobalsMap.get(fromFuncId.toString()));
      }
      if (!toFuncId
        || !enableStub
        || !toFuncFileId
        || !funcsOfVersionMap.get(toFuncId.toString())
        || !filesOfVersionMap.has(toFuncFileId.toString())) {
        return;
      }
      let key = toFuncName;
      if (!functions.isCFunction(funcsOfVersionMap.get(toFuncId.toString()))) {
        key = toFuncMangledId;
      }
      key += `@${filesOfVersionMap.get(toFuncFileId.toString()).path}`;
      func.stubs[key] = {};
    });
    // get merge invoke function variable global file
    const integrationGlobals = await functions.handleIntegratedGlobalsAnalysis(
      version,
      invokeFuncsVariableGlobalsMap.get(functionId),
      invokeFuncs.map((item) => ({ enableStub: item.enableStub, functionId: item.toFuncId })),
      invokeFuncsVariablesMap,
      filesOfVersionMap,
      globalsDefinedOfVersionMap
    );
    integrationGlobals.forEach((integrationGlobal) => {
      if (integrationGlobal.filePath) {
        relatedFilesSet.add(integrationGlobal.filePath);
      }
    });
    func.files = Array.from(relatedFilesSet);
  });
}
/**
 * 去除文件路径和函数名
 * @param {Object} param0 共用的参数
 * @param {Object} data 需要处理的数据
 * @param {String} type 数据类别
 * @returns
 */
function deleteVariableFilePathAndFuncName({
  func,
  filePathToFileIdMap,
  funcNameToFuncIdMap,
  staticGlobalFilesMap,
}, data, type) {
  const entryFuncFilePath = func.filePath;
  const entryFuncFileId = func.fileId.toString();
  const keyNames = Object.keys(data);
  return utility.arrayChunkOperation(keyNames, 10, async (keyName) => {
    // 处理文件路径
    if (keyName.indexOf('@') < 0) {
      return;
    }
    const filePath = keyName.substring(keyName.lastIndexOf('@') + 1, keyName.length);
    let file;
    if (!filePathToFileIdMap.has(filePath)) {
      file = await fileModel.getFileByPath(func.versionId, filePath);
      if (!file) {
        logger.info(`integration test generate testcase the file:"${filePath}" not found`);
        delete data[keyName];
        return;
      }
      filePathToFileIdMap.set(filePath, { fileId: file._id.toString() });
    }
    file = filePathToFileIdMap.get(filePath);
    let variableKey = keyName.replace(`@${filePath}`, '');
    const variable = data[keyName];
    if (filePath !== entryFuncFilePath) {
      variable.filePath = filePath;
      variable.fileId = filePathToFileIdMap.get(filePath).fileId;
    }
    // 处理@函数名
    const functionName = variableKey.substring(variableKey.lastIndexOf('@') + 1, variableKey.length);
    if (functionName !== variableKey) {
      variableKey = variableKey.replace(`@${functionName}`, '');
      if (!funcNameToFuncIdMap.has(functionName)) {
        const func = await functions.getFunctionByName(file.fileId, functionName);
        funcNameToFuncIdMap.set(functions.isCFunction(func) ? func.functionName : func.mangledId, { functionId: func._id.toString() });
      }
    }
    // 需要拼接上函数id
    if (type === 'statics') {
      variableKey += `@${funcNameToFuncIdMap.get(functionName).functionId}`;
    }
    // 如果是全局变量且全局变量是静态的需要处理@fileId 问题
    if (type === 'global'
      && staticGlobalFilesMap.has(variableKey)
      && staticGlobalFilesMap.get(variableKey).filter((f) => f.fileId !== variable.fileId).length > 0
      && variable.fileId !== entryFuncFileId) {
      variableKey += `@${variable.fileId}`;
    }
    // 已定义并且非入口函数所在文件的函数需要拼接文件id
    if (type === 'stubs' && filePath !== entryFuncFilePath) {
      const stub = await functions.getFunctionByName(file.fileId, functionName);
      if (stub) {
        variableKey += `@${variable.fileId}`;
        variable['@mangled'] = stub.mangledId;
        variable['@name'] = stub.functionName;
        variable['@attributes'] = stub.functionAttrs;
      }
    }
    delete data[keyName];
    data[variableKey] = variable;
  });
}
/**
 *  处理集成测试自动生成测试用例文件路径
 * @param {Object} func 被测函数信息
 * @param {Array[Object]} testcases 测试用例信息
 * @param {Map<globalName,Array[Object(file)]>} staticGlobalFilesMap 版本中所有静态全局变量对应的文件
 */
async function handleIntegrationAutoGenerateTestFilePath(func, testcases, filePathToFileIdMap, funcNameToFuncIdMap, staticGlobalFilesMap) {
  const commonParams = {
    func,
    filePathToFileIdMap,
    funcNameToFuncIdMap,
    staticGlobalFilesMap,
  };
  await utility.arrayChunkOperation(testcases, 10, async ({ testcase }) => {
    const inputFixedAddrs = testcase.fixedAddrs || {};
    // 绝对地址基地址
    if (inputFixedAddrs.bases) {
      await deleteVariableFilePathAndFuncName(commonParams, inputFixedAddrs.bases);
    }
    // 绝对地址表达式
    if (inputFixedAddrs.exprs) {
      await deleteVariableFilePathAndFuncName(commonParams, inputFixedAddrs.exprs);
    }
    // 桩函数
    if (testcase.stubs) {
      await deleteVariableFilePathAndFuncName(commonParams, testcase.stubs, 'stubs');
    }
    // 手动添加的函数
    if (testcase.funcs) {
      await deleteVariableFilePathAndFuncName(commonParams, testcase.funcs);
    }
    // 指针目标
    if (testcase.malloc) {
      await deleteVariableFilePathAndFuncName(commonParams, testcase.malloc);
    }
    const variables = testcase.variables || {};
    if (variables.global) {
      await deleteVariableFilePathAndFuncName(commonParams, variables.global, 'global');
    }
    if (variables.statics) {
      await deleteVariableFilePathAndFuncName(commonParams, variables.statics, 'statics');
    }
    // 处理输出检查项
    const output = testcase.output || {};
    if (output.malloc) {
      await deleteVariableFilePathAndFuncName(commonParams, output.malloc);
    }
    if (output.global) {
      await deleteVariableFilePathAndFuncName(commonParams, output.global, 'global');
    }
    if (output.malloc) {
      await deleteVariableFilePathAndFuncName(commonParams, output.malloc);
    }
    if (output.fixedAddrs) {
      await deleteVariableFilePathAndFuncName(commonParams, output.fixedAddrs);
    }
  });
}

async function resetTestNumber(functionId, locale) {
  functionId = mongoose.Types.ObjectId(functionId);
  const testsTree = await TestCaseGroupModel.getTree(null, functionId, locale);
  let testcaseNumber = 0;
  let testcaseGroupNumber = 0;
  const testcaseBulkUpdate = [];
  const testcaseGroupBulkUpdate = [];
  function handleTree(tree) {
    if (tree.children && utility.isArrayType(tree.children)) {
      tree.children.forEach((subTree) => {
        if (subTree.type === 'testcase') {
          testcaseNumber += 1;
          testcaseBulkUpdate.push({
            updateOne: {
              filter: { _id: mongoose.Types.ObjectId(subTree.testId) },
              update: { $set: { number: testcaseNumber } },
            },
          });
        } else {
          testcaseGroupNumber += 1;
          testcaseGroupBulkUpdate.push({
            updateOne: {
              filter: { _id: mongoose.Types.ObjectId(subTree.groupId) },
              update: { $set: { number: testcaseGroupNumber } },
            },
          });
          handleTree(subTree);
        }
      });
    }
  }
  handleTree(testsTree);
  return Promise.all([
    TestCase.bulkWrite(testcaseBulkUpdate),
    TestCaseGroup.bulkWrite(testcaseGroupBulkUpdate),
    numbers.restore(numbers.getTestcaseNumberName(functionId), testcaseNumber),
    numbers.restore(numbers.getTestcaseGroupNumberName(functionId), testcaseGroupNumber),
  ]);
}

eventHub.on([eventKind.EVENT_FUNCTION_REMOVE, eventKind.EVENT_FUNCTION_RESET], ({ functionIds, versionId }) => {
  function removeRelatedData(filter) {
    return Promise.all([
      TestCase.deleteMany(filter),
      TestCaseFieldConfig.deleteMany(filter),
      TestCaseCoveredField.deleteMany(filter),
      BreakPoint.deleteMany(filter),
    ]);
  }
  const filter = { versionId: mongoose.Types.ObjectId(versionId) };
  if (!functionIds) {
    return removeRelatedData(filter);
  }
  return utility.arrayChunkOperation(utility.arrayChunk(functionIds, 500), 1, (funcIds) => {
    filter.functionId = { $in: funcIds.map((id) => mongoose.Types.ObjectId(id)) };
    return removeRelatedData(filter);
  });
});

/**
 * Export model definition object.
 */
module.exports = {
  addStubVariadicParams,
  addTest,
  addDefaultValue,
  addFixedAddressDefaultValue,
  autoAddCaseId,

  clearTestcaseCoveredByFunc,
  coverageKind,
  completeTest,

  filter,
  formatTestCasesOutput,

  getFilesByTestId,
  getSummaryJson,
  getJson,
  getTest,
  getTestCaseFiles,
  getTestIdentifier,
  getTests,
  getTestCaseValues,
  getTestOptions,
  getTestsOfFunctions,
  getTestsOfFile,
  getTestResultsGroup,
  getCount,
  getGroupCount,
  getTestFieldConfig,
  getOverview,
  generateOverviewXlsx,
  getTestFieldConfigByVerisonId,

  importTests,
  import614TestCases,
  initFunctionTestFieldConfig,
  integrationGetFilesTypeSystemByTestId,
  integrationTestGenerateTestStubsAndFiles,

  removeCFGTestIds,
  resultKind: testResultKind,
  replaceImportTestCasePath,
  replaceFileIdOrFuncIdToPath,
  removeResultOfExecutionInVersion,
  removeResultOfExecutionInFunction,
  removeResultOfExecution,
  removeOutputGlobalVariable,
  removeOutputMallocVariable,
  removeOutputObjectVariable,
  removeTest,
  removeFixedAddress,
  removeFixedAddressValue,
  removeFixedAddressValues,
  removeFixedAddressExpr,
  removeFixedAddressPointerTargets,
  removeMallocVariable,
  removeStub,
  removeStubPointerTarget,
  removeMallocVariableValue,
  removeStubValue,
  removeStubPointerTargetValue,
  removeOutputFixedAddress,
  removeOutputIntegratedFunc,
  removeOutputStaticsVariable,
  removeEmptyFixedAddressOfTestCase,
  removeFixedAddressPointerTargetsValues,
  resultCopy,
  replaceGeneratedTests,
  renameFixedAddress,
  renameMallocVariable,
  renameStub,
  renameStubPointerTarget,
  reshuffleTestTypeNumber,
  resetTestTypeNumber,
  resetTestTypeNumberSequentially,
  reshuffleTestNumber,
  removeOutputResult,
  removeResult,
  resetTestNumber,

  updateMallocVariable,
  updateStub,
  updateStubPointerTarget,
  updateTestcaseComment,
  updateArrayIndexRange,
  updateTestFieldConfig,
  updateTest,
  updateTestOutputResult,
  updateTestResult,

  peekNextTypeNumberString,

  handleRuntimeErrorResult,
  handleTestCaseFiles,
  handleIntegrationAutoGenerateTestFilePath,

  mcdcKind,

  testType,
  testKind,
  testCountOverview,

  stubKind,
  stubTriggerKind,

  moveTestCases,

  removeTestsByFunction,
};
exports = Object.assign(exports, module.exports);
